diff --git a/.eslintrc.json b/.eslintrc.json index bffb357a..fc0b1b8c 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,3 +1,19 @@ { - "extends": "next/core-web-vitals" + "extends": ["airbnb", "next/core-web-vitals", "prettier"], + "plugins": ["prettier"], + "rules": { + "import/order": [ + "error", + { + "newlines-between": "always" + } + ] + }, + "settings": { + "import/resolver": { + "node": { + "paths": ["src"] + } + } + } } diff --git a/package.json b/package.json index 355178f8..8db6e8f6 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "dependencies": { "@headlessui/react": "^1.6.6", "@tailwindcss/forms": "^0.5.3", + "classnames": "^2.3.1", "dockerode": "^3.3.4", "js-yaml": "^4.1.0", "json-rpc-2.0": "^1.4.1", @@ -27,8 +28,16 @@ "devDependencies": { "autoprefixer": "^10.4.8", "eslint": "8.22.0", + "eslint-config-airbnb": "^19.0.4", "eslint-config-next": "12.2.5", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-jsx-a11y": "^6.6.1", + "eslint-plugin-prettier": "^4.2.1", + "eslint-plugin-react": "^7.30.1", + "eslint-plugin-react-hooks": "^4.6.0", "postcss": "^8.4.16", + "prettier": "^2.7.1", "tailwindcss": "^3.1.8", "typescript": "^4.8.2" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cfe2276f..c58fee0a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,15 +4,24 @@ specifiers: '@headlessui/react': ^1.6.6 '@tailwindcss/forms': ^0.5.3 autoprefixer: ^10.4.8 + classnames: ^2.3.1 dockerode: ^3.3.4 eslint: 8.22.0 + eslint-config-airbnb: ^19.0.4 eslint-config-next: 12.2.5 + eslint-config-prettier: ^8.5.0 + eslint-plugin-import: ^2.26.0 + eslint-plugin-jsx-a11y: ^6.6.1 + eslint-plugin-prettier: ^4.2.1 + eslint-plugin-react: ^7.30.1 + eslint-plugin-react-hooks: ^4.6.0 js-yaml: ^4.1.0 json-rpc-2.0: ^1.4.1 memory-cache: ^0.2.0 next: 12.2.5 node-os-utils: ^1.3.7 postcss: ^8.4.16 + prettier: ^2.7.1 raw-body: ^2.5.1 react: 18.2.0 react-dom: 18.2.0 @@ -25,6 +34,7 @@ specifiers: dependencies: '@headlessui/react': 1.6.6_biqbaboplfbrettd7655fr4n2y '@tailwindcss/forms': 0.5.3_tailwindcss@3.1.8 + classnames: 2.3.1 dockerode: 3.3.4 js-yaml: 4.1.0 json-rpc-2.0: 1.4.1 @@ -41,8 +51,16 @@ dependencies: devDependencies: autoprefixer: 10.4.8_postcss@8.4.16 eslint: 8.22.0 + eslint-config-airbnb: 19.0.4_ujj5bqj46ej3re666g22wkx75e eslint-config-next: 12.2.5_shit3uhl6a7megkzgoz6xssnfa + eslint-config-prettier: 8.5.0_eslint@8.22.0 + eslint-plugin-import: 2.26.0_eslint@8.22.0 + eslint-plugin-jsx-a11y: 6.6.1_eslint@8.22.0 + eslint-plugin-prettier: 4.2.1_i2cojdczqdiurzgttlwdgf764e + eslint-plugin-react: 7.31.5_eslint@8.22.0 + eslint-plugin-react-hooks: 4.6.0_eslint@8.22.0 postcss: 8.4.16 + prettier: 2.7.1 tailwindcss: 3.1.8_postcss@8.4.16 typescript: 4.8.2 @@ -604,6 +622,10 @@ packages: resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} dev: false + /classnames/2.3.1: + resolution: {integrity: sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==} + dev: false + /color-convert/2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -625,6 +647,10 @@ packages: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} dev: true + /confusing-browser-globals/1.0.11: + resolution: {integrity: sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==} + dev: true + /core-js-pure/3.25.0: resolution: {integrity: sha512-IeHpLwk3uoci37yoI2Laty59+YqH9x5uR65/yiA0ARAJrTrN4YU0rmauLWfvqOuk77SlNJXj2rM6oT/dBD87+A==} requiresBuild: true @@ -843,6 +869,41 @@ packages: engines: {node: '>=10'} dev: true + /eslint-config-airbnb-base/15.0.0_2iahngt3u2tkbdlu6s4gkur3pu: + resolution: {integrity: sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==} + engines: {node: ^10.12.0 || >=12.0.0} + peerDependencies: + eslint: ^7.32.0 || ^8.2.0 + eslint-plugin-import: ^2.25.2 + dependencies: + confusing-browser-globals: 1.0.11 + eslint: 8.22.0 + eslint-plugin-import: 2.26.0_eslint@8.22.0 + object.assign: 4.1.4 + object.entries: 1.1.5 + semver: 6.3.0 + dev: true + + /eslint-config-airbnb/19.0.4_ujj5bqj46ej3re666g22wkx75e: + resolution: {integrity: sha512-T75QYQVQX57jiNgpF9r1KegMICE94VYwoFQyMGhrvc+lB8YF2E/M/PYDaQe1AJcWaEgqLE+ErXV1Og/+6Vyzew==} + engines: {node: ^10.12.0 || ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^7.32.0 || ^8.2.0 + eslint-plugin-import: ^2.25.3 + eslint-plugin-jsx-a11y: ^6.5.1 + eslint-plugin-react: ^7.28.0 + eslint-plugin-react-hooks: ^4.3.0 + dependencies: + eslint: 8.22.0 + eslint-config-airbnb-base: 15.0.0_2iahngt3u2tkbdlu6s4gkur3pu + eslint-plugin-import: 2.26.0_eslint@8.22.0 + eslint-plugin-jsx-a11y: 6.6.1_eslint@8.22.0 + eslint-plugin-react: 7.31.5_eslint@8.22.0 + eslint-plugin-react-hooks: 4.6.0_eslint@8.22.0 + object.assign: 4.1.4 + object.entries: 1.1.5 + dev: true + /eslint-config-next/12.2.5_shit3uhl6a7megkzgoz6xssnfa: resolution: {integrity: sha512-SOowilkqPzW6DxKp3a3SYlrfPi5Ajs9MIzp9gVfUDxxH9QFM5ElkR1hX5m/iICJuvCbWgQqFBiA3mCMozluniw==} peerDependencies: @@ -868,6 +929,15 @@ packages: - supports-color dev: true + /eslint-config-prettier/8.5.0_eslint@8.22.0: + resolution: {integrity: sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + dependencies: + eslint: 8.22.0 + dev: true + /eslint-import-resolver-node/0.3.6: resolution: {integrity: sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==} dependencies: @@ -925,6 +995,64 @@ packages: - supports-color dev: true + /eslint-module-utils/2.7.4_7gfxlqsjhuntdifxknjgbjwpbu: + resolution: {integrity: sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + dependencies: + debug: 3.2.7 + eslint: 8.22.0 + eslint-import-resolver-node: 0.3.6 + transitivePeerDependencies: + - supports-color + dev: true + + /eslint-plugin-import/2.26.0_eslint@8.22.0: + resolution: {integrity: sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + dependencies: + array-includes: 3.1.5 + array.prototype.flat: 1.3.0 + debug: 2.6.9 + doctrine: 2.1.0 + eslint: 8.22.0 + eslint-import-resolver-node: 0.3.6 + eslint-module-utils: 2.7.4_7gfxlqsjhuntdifxknjgbjwpbu + has: 1.0.3 + is-core-module: 2.10.0 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.values: 1.1.5 + resolve: 1.22.1 + tsconfig-paths: 3.14.1 + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + dev: true + /eslint-plugin-import/2.26.0_oqagwj4pgbbpoeodu52b4a34xi: resolution: {integrity: sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==} engines: {node: '>=4'} @@ -978,6 +1106,23 @@ packages: semver: 6.3.0 dev: true + /eslint-plugin-prettier/4.2.1_i2cojdczqdiurzgttlwdgf764e: + resolution: {integrity: sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==} + engines: {node: '>=12.0.0'} + peerDependencies: + eslint: '>=7.28.0' + eslint-config-prettier: '*' + prettier: '>=2.0.0' + peerDependenciesMeta: + eslint-config-prettier: + optional: true + dependencies: + eslint: 8.22.0 + eslint-config-prettier: 8.5.0_eslint@8.22.0 + prettier: 2.7.1 + prettier-linter-helpers: 1.0.0 + dev: true + /eslint-plugin-react-hooks/4.6.0_eslint@8.22.0: resolution: {integrity: sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==} engines: {node: '>=10'} @@ -1123,6 +1268,10 @@ packages: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} dev: true + /fast-diff/1.2.0: + resolution: {integrity: sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==} + dev: true + /fast-glob/3.2.11: resolution: {integrity: sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==} engines: {node: '>=8.6.0'} @@ -1931,6 +2080,19 @@ packages: engines: {node: '>= 0.8.0'} dev: true + /prettier-linter-helpers/1.0.0: + resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} + engines: {node: '>=6.0.0'} + dependencies: + fast-diff: 1.2.0 + dev: true + + /prettier/2.7.1: + resolution: {integrity: sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==} + engines: {node: '>=10.13.0'} + hasBin: true + dev: true + /prop-types/15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} dependencies: diff --git a/src/components/bookmarks/item.jsx b/src/components/bookmarks/item.jsx index 380699bb..560bcb1a 100644 --- a/src/components/bookmarks/item.jsx +++ b/src/components/bookmarks/item.jsx @@ -2,20 +2,22 @@ export default function Item({ bookmark }) { const { hostname } = new URL(bookmark.href); return ( - <li - onClick={() => { - window.open(bookmark.href, "_blank").focus(); - }} - key={bookmark.name} - className="mb-3 cursor-pointer flex rounded-md font-medium text-theme-700 hover:text-theme-800 dark:text-theme-200 dark:hover:text-theme-300 shadow-md shadow-theme-900/10 dark:shadow-theme-900/50 bg-white/50 hover:bg-theme-300/10 dark:bg-white/5 dark:hover:bg-white/10" - > - <div className="flex-shrink-0 flex items-center justify-center w-11 bg-theme-500/10 dark:bg-theme-900/50 text-theme-700 dark:text-theme-200 text-sm font-medium rounded-l-md"> - {bookmark.abbr} - </div> - <div className="flex-1 flex items-center justify-between rounded-r-md "> - <div className="flex-1 grow pl-3 py-2 text-xs">{bookmark.name}</div> - <div className="px-2 py-2 truncate text-theme-500 dark:text-theme-400 opacity-50 text-xs">{hostname}</div> - </div> + <li key={bookmark.name}> + <button + type="button" + onClick={() => window.open(bookmark.href, "_blank").focus()} + className="w-full text-left mb-3 cursor-pointer rounded-md font-medium text-theme-700 hover:text-theme-800 dark:text-theme-200 dark:hover:text-theme-300 shadow-md shadow-theme-900/10 dark:shadow-theme-900/50 bg-white/50 hover:bg-theme-300/10 dark:bg-white/5 dark:hover:bg-white/10" + > + <div className="flex"> + <div className="flex-shrink-0 flex items-center justify-center w-11 bg-theme-500/10 dark:bg-theme-900/50 text-theme-700 dark:text-theme-200 text-sm font-medium rounded-l-md"> + {bookmark.abbr} + </div> + <div className="flex-1 flex items-center justify-between rounded-r-md "> + <div className="flex-1 grow pl-3 py-2 text-xs">{bookmark.name}</div> + <div className="px-2 py-2 truncate text-theme-500 dark:text-theme-400 opacity-50 text-xs">{hostname}</div> + </div> + </div> + </button> </li> ); } diff --git a/src/components/bookmarks/list.jsx b/src/components/bookmarks/list.jsx index 2c080e02..3b3774c9 100644 --- a/src/components/bookmarks/list.jsx +++ b/src/components/bookmarks/list.jsx @@ -2,7 +2,7 @@ import Item from "components/bookmarks/item"; export default function List({ bookmarks }) { return ( - <ul role="list" className="mt-3 flex flex-col"> + <ul className="mt-3 flex flex-col"> {bookmarks.map((bookmark) => ( <Item key={bookmark.name} bookmark={bookmark} /> ))} diff --git a/src/components/color-toggle.jsx b/src/components/color-toggle.jsx index 1f9fe84c..3b98c995 100644 --- a/src/components/color-toggle.jsx +++ b/src/components/color-toggle.jsx @@ -1,7 +1,7 @@ -import { useContext } from "react"; +import { useContext, Fragment } from "react"; import { IoColorPalette } from "react-icons/io5"; import { Popover, Transition } from "@headlessui/react"; -import { Fragment } from "react"; +import classNames from "classnames"; import { ColorContext } from "utils/color-context"; @@ -39,42 +39,38 @@ export default function ColorToggle() { return ( <div className="w-full self-center"> <Popover className="relative flex items-center"> - {({ open }) => ( - <> - <Popover.Button className="outline-none"> - <IoColorPalette - className="h-5 w-5 text-theme-800 dark:text-theme-200 transition duration-150 ease-in-out" - aria-hidden="true" - /> - </Popover.Button> - <Transition - as={Fragment} - enter="transition ease-out duration-200" - enterFrom="opacity-0 translate-y-1" - enterTo="opacity-100 translate-y-0" - leave="transition ease-in duration-150" - leaveFrom="opacity-100 translate-y-0" - leaveTo="opacity-0 translate-y-1" - > - <Popover.Panel className="absolute -top-[75px] left-0"> - <div className="rounded-md shadow-lg ring-1 ring-black ring-opacity-5"> - <div className="relative grid gap-2 p-2 grid-cols-11 shadow-theme-900/10 dark:shadow-theme-900 rounded-md shadow-md"> - {colors.map((color) => ( - <button role="button" onClick={() => setColor(color)} key={color}> - <div - className={ - (active == color ? "border-2" : "border-0") + - ` rounded-md w-5 h-5 border-black/50 dark:border-white/50 theme-${color} bg-theme-500` - } - /> - </button> - ))} - </div> - </div> - </Popover.Panel> - </Transition> - </> - )} + <Popover.Button className="outline-none"> + <IoColorPalette + className="h-5 w-5 text-theme-800 dark:text-theme-200 transition duration-150 ease-in-out" + aria-hidden="true" + /> + </Popover.Button> + <Transition + as={Fragment} + enter="transition ease-out duration-200" + enterFrom="opacity-0 translate-y-1" + enterTo="opacity-100 translate-y-0" + leave="transition ease-in duration-150" + leaveFrom="opacity-100 translate-y-0" + leaveTo="opacity-0 translate-y-1" + > + <Popover.Panel className="absolute -top-[75px] left-0"> + <div className="rounded-md shadow-lg ring-1 ring-black ring-opacity-5"> + <div className="relative grid gap-2 p-2 grid-cols-11 shadow-theme-900/10 dark:shadow-theme-900 rounded-md shadow-md"> + {colors.map((color) => ( + <button type="button" onClick={() => setColor(color)} key={color}> + <div + className={classNames( + active === color ? "border-2" : "border-0", + `rounded-md w-5 h-5 border-black/50 dark:border-white/50 theme-${color} bg-theme-500` + )} + /> + </button> + ))} + </div> + </div> + </Popover.Panel> + </Transition> </Popover> </div> ); diff --git a/src/components/greeting.jsx b/src/components/greeting.jsx index 5412a0fc..c660ecd9 100644 --- a/src/components/greeting.jsx +++ b/src/components/greeting.jsx @@ -1,5 +1,4 @@ export default function Greeting() { - const name = process.env.NEXT_PUBLIC_DISPLAY_NAME; const hour = new Date().getHours(); let day = "day"; @@ -12,9 +11,5 @@ export default function Greeting() { day = "evening"; } - return ( - <div className="self-end grow text-2xl font-thin text-theme-800 dark:text-theme-200"> - Good {day} - </div> - ); + return <div className="self-end grow text-2xl font-thin text-theme-800 dark:text-theme-200">Good {day}</div>; } diff --git a/src/components/modal.jsx b/src/components/modal.jsx deleted file mode 100644 index f683b5e9..00000000 --- a/src/components/modal.jsx +++ /dev/null @@ -1,68 +0,0 @@ -import { Fragment, useRef, useState, Children } from "react"; -import { Dialog, Transition } from "@headlessui/react"; - -function classNames(...classes) { - return classes.filter(Boolean).join(" "); -} - -const Modal = ({ Toggle, Content }) => { - const [open, setOpen] = useState(false); - const cancelButtonRef = useRef(null); - - return ( - <> - <Toggle open={open} setOpen={setOpen} /> - <Transition.Root show={open} as={Fragment}> - <Dialog - as="div" - className="relative z-10" - initialFocus={cancelButtonRef} - onClose={setOpen} - > - <Transition.Child - as={Fragment} - enter="ease-out duration-300" - enterFrom="opacity-0" - enterTo="opacity-100" - leave="ease-in duration-200" - leaveFrom="opacity-100" - leaveTo="opacity-0" - > - <div className="fixed inset-0 bg-theme-900/90 transition-opacity" /> - </Transition.Child> - - <div className="fixed z-10 inset-0 overflow-y-auto"> - <div className="flex items-center justify-center min-h-full"> - <Transition.Child - as={Fragment} - enter="ease-out duration-300" - enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" - enterTo="opacity-100 translate-y-0 sm:scale-100" - leave="ease-in duration-200" - leaveFrom="opacity-100 translate-y-0 sm:scale-100" - leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" - > - <Dialog.Panel className="relative rounded-lg shadow-xl transform transition-all my-8 max-w-lg w-full"> - <Content open={open} setOpen={setOpen} /> - </Dialog.Panel> - </Transition.Child> - </div> - </div> - </Dialog> - </Transition.Root> - </> - ); -}; - -const ModalToggle = ({ open, setOpen, children }) => ( - <div onClick={() => setOpen(!open)}>{children}</div> -); - -const ModalContent = ({ open, setOpen, children }) => ( - <div className="body">{children}</div> -); - -Modal.Toggle = ModalToggle; -Modal.Content = ModalContent; - -export default Modal; diff --git a/src/components/services/item.jsx b/src/components/services/item.jsx index fffc6806..4cb5a055 100644 --- a/src/components/services/item.jsx +++ b/src/components/services/item.jsx @@ -8,28 +8,32 @@ import Docker from "./widgets/service/docker"; function resolveIcon(icon) { if (icon.startsWith("http")) { return `/api/proxy?url=${encodeURIComponent(icon)}`; - } else if (icon.startsWith("/")) { - return icon; - } else { - if (icon.endsWith(".png")) { - return `https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/${icon}`; - } else { - return `https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/${icon}.png`; - } } + + if (icon.startsWith("/")) { + return icon; + } + + if (icon.endsWith(".png")) { + return `https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/${icon}`; + } + + return `https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/${icon}.png`; } export default function Item({ service }) { return ( <li key={service.name}> <Disclosure> - <div className={ - (service.href && service.href !== "#" ? 'cursor-pointer ' : 'cursor-default ') + - 'transition-all h-15 overflow-hidden mb-3 p-1 rounded-md font-medium text-theme-700 hover:text-theme-800 dark:text-theme-200 dark:hover:text-theme-300 shadow-md shadow-theme-900/10 dark:shadow-theme-900/40 bg-white/50 hover:bg-theme-300/10 dark:bg-white/5 dark:hover:bg-white/10' - }> + <div + className={`${ + service.href && service.href !== "#" ? "cursor-pointer " : "cursor-default " + }transition-all h-15 overflow-hidden mb-3 p-1 rounded-md font-medium text-theme-700 hover:text-theme-800 dark:text-theme-200 dark:hover:text-theme-300 shadow-md shadow-theme-900/10 dark:shadow-theme-900/40 bg-white/50 hover:bg-theme-300/10 dark:bg-white/5 dark:hover:bg-white/10`} + > <div className="flex"> {service.icon && ( - <div + <button + type="button" onClick={() => { if (service.href && service.href !== "#") { window.open(service.href, "_blank").focus(); @@ -38,10 +42,11 @@ export default function Item({ service }) { className="flex-shrink-0 flex items-center justify-center w-12 " > <Image src={resolveIcon(service.icon)} width={32} height={32} alt="logo" /> - </div> + </button> )} - <div + <button + type="button" onClick={() => { if (service.href && service.href !== "#") { window.open(service.href, "_blank").focus(); @@ -53,9 +58,12 @@ export default function Item({ service }) { {service.name} <p className="text-theme-500 dark:text-theme-400 text-xs font-extralight">{service.description}</p> </div> - </div> + </button> {service.container && ( - <Disclosure.Button as="div" className="flex-shrink-0 flex items-center justify-center w-12 cursor-pointer"> + <Disclosure.Button + as="div" + className="flex-shrink-0 flex items-center justify-center w-12 cursor-pointer" + > <Status service={service} /> </Disclosure.Button> )} diff --git a/src/components/services/list.jsx b/src/components/services/list.jsx index ee519137..b6c60cc8 100644 --- a/src/components/services/list.jsx +++ b/src/components/services/list.jsx @@ -2,7 +2,7 @@ import Item from "components/services/item"; export default function List({ services }) { return ( - <ul role="list" className="mt-3 flex flex-col"> + <ul className="mt-3 flex flex-col"> {services.map((service) => ( <Item key={service.name} service={service} /> ))} diff --git a/src/components/services/status.jsx b/src/components/services/status.jsx index b2c0ea4c..dc903408 100644 --- a/src/components/services/status.jsx +++ b/src/components/services/status.jsx @@ -1,28 +1,18 @@ import useSWR from "swr"; export default function Status({ service }) { - const { data, error } = useSWR( - `/api/docker/status/${service.container}/${service.server || ""}` - ); + const { data, error } = useSWR(`/api/docker/status/${service.container}/${service.server || ""}`); if (error) { - return ( - <div className="w-3 h-3 bg-rose-300 dark:bg-rose-500 rounded-full" /> - ); + return <div className="w-3 h-3 bg-rose-300 dark:bg-rose-500 rounded-full" />; } if (data && data.status === "running") { - return ( - <div className="w-3 h-3 bg-emerald-300 dark:bg-emerald-500 rounded-full" /> - ); + return <div className="w-3 h-3 bg-emerald-300 dark:bg-emerald-500 rounded-full" />; } if (data && data.status === "not found") { - return ( - <> - <div className="h-2.5 w-2.5 bg-orange-400/50 dark:bg-yellow-200/40 -rotate-45"></div> - </> - ); + return <div className="h-2.5 w-2.5 bg-orange-400/50 dark:bg-yellow-200/40 -rotate-45" />; } return <div className="w-3 h-3 bg-black/20 dark:bg-white/40 rounded-full" />; diff --git a/src/components/services/widgets/service/emby.jsx b/src/components/services/widgets/service/emby.jsx index ac964566..c9e6e419 100644 --- a/src/components/services/widgets/service/emby.jsx +++ b/src/components/services/widgets/service/emby.jsx @@ -24,9 +24,9 @@ export default function Emby({ service, title = "Emby" }) { ); } - const playing = sessionsData.filter((session) => session.hasOwnProperty("NowPlayingItem")); + const playing = sessionsData.filter((session) => session?.NowPlayingItem); const transcoding = sessionsData.filter( - (session) => session.hasOwnProperty("PlayState") && session.PlayState.PlayMethod === "Transcode" + (session) => session?.PlayState && session.PlayState.PlayMethod === "Transcode" ); const bitrate = playing.reduce((acc, session) => acc + session.NowPlayingItem.Bitrate, 0); diff --git a/src/components/services/widgets/service/rutorrent.jsx b/src/components/services/widgets/service/rutorrent.jsx index 5dd11333..ff9fb2a3 100644 --- a/src/components/services/widgets/service/rutorrent.jsx +++ b/src/components/services/widgets/service/rutorrent.jsx @@ -25,13 +25,9 @@ export default function Rutorrent({ service }) { ); } - const upload = statusData.reduce((acc, torrent) => { - return acc + parseInt(torrent["d.get_up_rate"]); - }, 0); + const upload = statusData.reduce((acc, torrent) => acc + parseInt(torrent["d.get_up_rate"], 10), 0); - const download = statusData.reduce((acc, torrent) => { - return acc + parseInt(torrent["d.get_down_rate"]); - }, 0); + const download = statusData.reduce((acc, torrent) => acc + parseInt(torrent["d.get_down_rate"], 10), 0); const active = statusData.filter((torrent) => torrent["d.get_state"] === "1"); diff --git a/src/components/services/widgets/service/tautulli.jsx b/src/components/services/widgets/service/tautulli.jsx index 32fe91b3..a3dce1c1 100644 --- a/src/components/services/widgets/service/tautulli.jsx +++ b/src/components/services/widgets/service/tautulli.jsx @@ -24,7 +24,7 @@ export default function Tautulli({ service }) { ); } - const data = statsData.response.data; + const { data } = statsData.response; return ( <Widget> diff --git a/src/components/widgets/openweathermap/icon.jsx b/src/components/widgets/openweathermap/icon.jsx index 975c1f7e..da65bc91 100644 --- a/src/components/widgets/openweathermap/icon.jsx +++ b/src/components/widgets/openweathermap/icon.jsx @@ -1,7 +1,7 @@ import mapIcon from "utils/owm-condition-map"; export default function Icon({ condition, timeOfDay }) { - const Icon = mapIcon(condition, timeOfDay); + const IconComponent = mapIcon(condition, timeOfDay); - return <Icon className="w-10 h-10 text-theme-800 dark:text-theme-200"></Icon>; + return <IconComponent className="w-10 h-10 text-theme-800 dark:text-theme-200" />; } diff --git a/src/components/widgets/openweathermap/weather.jsx b/src/components/widgets/openweathermap/weather.jsx index a2074353..c1ab9f05 100644 --- a/src/components/widgets/openweathermap/weather.jsx +++ b/src/components/widgets/openweathermap/weather.jsx @@ -6,7 +6,7 @@ import Icon from "./icon"; export default function OpenWeatherMap({ options }) { const { data, error } = useSWR(`/api/widgets/openweathermap?${new URLSearchParams(options).toString()}`); - if (error || data?.cod == 401) { + if (error || data?.cod === 401) { return ( <div className="flex flex-col"> <div className="flex flex-row items-center justify-end"> @@ -23,11 +23,11 @@ export default function OpenWeatherMap({ options }) { } if (!data) { - return <div className="flex flex-row items-center"></div>; + return <div className="flex flex-row items-center" />; } if (data.error) { - return <div className="flex flex-row items-center"></div>; + return <div className="flex flex-row items-center" />; } return ( diff --git a/src/components/widgets/resources/disk.jsx b/src/components/widgets/resources/disk.jsx index e181c604..f78a3a2e 100644 --- a/src/components/widgets/resources/disk.jsx +++ b/src/components/widgets/resources/disk.jsx @@ -1,6 +1,7 @@ import useSWR from "swr"; import { FiHardDrive } from "react-icons/fi"; import { BiError } from "react-icons/bi"; + import { formatBytes } from "utils/stats-helpers"; export default function Disk({ options }) { diff --git a/src/components/widgets/resources/memory.jsx b/src/components/widgets/resources/memory.jsx index 9f2b341c..e8d60ccc 100644 --- a/src/components/widgets/resources/memory.jsx +++ b/src/components/widgets/resources/memory.jsx @@ -1,6 +1,7 @@ import useSWR from "swr"; import { FaMemory } from "react-icons/fa"; import { BiError } from "react-icons/bi"; + import { formatBytes } from "utils/stats-helpers"; export default function Memory() { diff --git a/src/components/widgets/resources/resources.jsx b/src/components/widgets/resources/resources.jsx index d5b7705f..711e97fb 100644 --- a/src/components/widgets/resources/resources.jsx +++ b/src/components/widgets/resources/resources.jsx @@ -4,19 +4,17 @@ import Memory from "./memory"; export default function Resources({ options }) { return ( - <> - <div className="flex flex-col max-w:full basis-1/2 sm:basis-auto self-center"> - <div className="flex flex-row space-x-4 self-center"> - {options.cpu && <Cpu />} - {options.memory && <Memory />} - {options.disk && <Disk options={options} />} - </div> - {options.label && ( - <div className="border-t-2 border-theme-800 dark:border-theme-200 mt-1 pt-1 text-center text-theme-800 dark:text-theme-200 text-xs"> - {options.label} - </div> - )} + <div className="flex flex-col max-w:full basis-1/2 sm:basis-auto self-center"> + <div className="flex flex-row space-x-4 self-center"> + {options.cpu && <Cpu />} + {options.memory && <Memory />} + {options.disk && <Disk options={options} />} </div> - </> + {options.label && ( + <div className="border-t-2 border-theme-800 dark:border-theme-200 mt-1 pt-1 text-center text-theme-800 dark:text-theme-200 text-xs"> + {options.label} + </div> + )} + </div> ); } diff --git a/src/components/widgets/search/search.jsx b/src/components/widgets/search/search.jsx index 48ba3b2e..d1805d72 100644 --- a/src/components/widgets/search/search.jsx +++ b/src/components/widgets/search/search.jsx @@ -30,7 +30,7 @@ export default function Search({ options }) { const [query, setQuery] = useState(""); if (!provider) { - return <></>; + return null; } function handleSubmit(event) { @@ -49,11 +49,10 @@ export default function Search({ options }) { return ( <form className="flex-col relative h-8 my-4 min-w-full md:min-w-fit grow" onSubmit={handleSubmit}> - <div className="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none w-full text-theme-800 dark:text-theme-200"></div> + <div className="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none w-full text-theme-800 dark:text-theme-200" /> <input type="search" - autoFocus - className={`overflow-hidden w-full placeholder-theme-900 text-xs text-theme-900 bg-theme-50 rounded-md border border-theme-300 focus:ring-theme-500 focus:border-theme-500 dark:bg-theme-800 dark:border-theme-600 dark:placeholder-theme-400 dark:text-white dark:focus:ring-theme-500 dark:focus:border-theme-500 h-full`} + className="overflow-hidden w-full placeholder-theme-900 text-xs text-theme-900 bg-theme-50 rounded-md border border-theme-300 focus:ring-theme-500 focus:border-theme-500 dark:bg-theme-800 dark:border-theme-600 dark:placeholder-theme-400 dark:text-white dark:focus:ring-theme-500 dark:focus:border-theme-500 h-full" placeholder="Search..." onChange={(s) => setQuery(s.currentTarget.value)} required diff --git a/src/components/widgets/weather/icon.jsx b/src/components/widgets/weather/icon.jsx index b6105254..e501b0a8 100644 --- a/src/components/widgets/weather/icon.jsx +++ b/src/components/widgets/weather/icon.jsx @@ -1,7 +1,7 @@ import mapIcon from "utils/condition-map"; export default function Icon({ condition, timeOfDay }) { - const Icon = mapIcon(condition, timeOfDay); + const IconComponent = mapIcon(condition, timeOfDay); - return <Icon className="w-10 h-10 text-theme-800 dark:text-theme-200"></Icon>; + return <IconComponent className="w-10 h-10 text-theme-800 dark:text-theme-200" />; } diff --git a/src/components/widgets/weather/weather.jsx b/src/components/widgets/weather/weather.jsx index 87d03786..36ca9cbd 100644 --- a/src/components/widgets/weather/weather.jsx +++ b/src/components/widgets/weather/weather.jsx @@ -4,7 +4,6 @@ import { BiError } from "react-icons/bi"; import Icon from "./icon"; export default function WeatherApi({ options }) { - console.log(options); const { data, error } = useSWR(`/api/widgets/weather?${new URLSearchParams(options).toString()}`); if (error) { @@ -24,11 +23,11 @@ export default function WeatherApi({ options }) { } if (!data) { - return <div className="flex flex-row items-center justify-end"></div>; + return <div className="flex flex-row items-center justify-end" />; } if (data.error) { - return <div className="flex flex-row items-center justify-end"></div>; + return <div className="flex flex-row items-center justify-end" />; } return ( diff --git a/src/pages/_app.js b/src/pages/_app.jsx similarity index 88% rename from src/pages/_app.js rename to src/pages/_app.jsx index 58e3293b..7c12e38f 100644 --- a/src/pages/_app.js +++ b/src/pages/_app.jsx @@ -1,3 +1,4 @@ +/* eslint-disable react/jsx-props-no-spreading */ import { SWRConfig } from "swr"; import "styles/globals.css"; import "styles/weather-icons.css"; diff --git a/src/pages/_document.js b/src/pages/_document.jsx similarity index 100% rename from src/pages/_document.js rename to src/pages/_document.jsx diff --git a/src/pages/api/bookmarks.js b/src/pages/api/bookmarks.js index ee77065d..c50add44 100644 --- a/src/pages/api/bookmarks.js +++ b/src/pages/api/bookmarks.js @@ -1,6 +1,8 @@ import { promises as fs } from "fs"; import path from "path"; + import yaml from "js-yaml"; + import checkAndCopyConfig from "utils/config"; export default async function handler(req, res) { @@ -11,17 +13,13 @@ export default async function handler(req, res) { const bookmarks = yaml.load(fileContents); // map easy to write YAML objects into easy to consume JS arrays - const bookmarksArray = bookmarks.map((group) => { - return { - name: Object.keys(group)[0], - bookmarks: group[Object.keys(group)[0]].map((entries) => { - return { - name: Object.keys(entries)[0], - ...entries[Object.keys(entries)[0]][0], - }; - }), - }; - }); + const bookmarksArray = bookmarks.map((group) => ({ + name: Object.keys(group)[0], + bookmarks: group[Object.keys(group)[0]].map((entries) => ({ + name: Object.keys(entries)[0], + ...entries[Object.keys(entries)[0]][0], + })), + })); res.send(bookmarksArray); } diff --git a/src/pages/api/docker/stats/[...service].js b/src/pages/api/docker/stats/[...service].js index 34f387b7..382a8924 100644 --- a/src/pages/api/docker/stats/[...service].js +++ b/src/pages/api/docker/stats/[...service].js @@ -1,4 +1,5 @@ import Docker from "dockerode"; + import getDockerArguments from "utils/docker"; export default async function handler(req, res) { @@ -21,30 +22,30 @@ export default async function handler(req, res) { // bad docker connections can result in a <Buffer ...> object? // in any case, this ensures the result is the expected array if (!Array.isArray(containers)) { - return res.status(500).send({ + res.status(500).send({ error: "query failed", }); + return; } - const containerNames = containers.map((container) => { - return container.Names[0].replace(/^\//, ""); - }); + const containerNames = containers.map((container) => container.Names[0].replace(/^\//, "")); const containerExists = containerNames.includes(containerName); if (!containerExists) { - return res.status(200).send({ + res.status(200).send({ error: "not found", }); + return; } const container = docker.getContainer(containerName); const stats = await container.stats({ stream: false }); - return res.status(200).json({ - stats: stats, + res.status(200).json({ + stats, }); } catch { - return res.status(500).send({ + res.status(500).send({ error: "unknown error", }); } diff --git a/src/pages/api/docker/status/[...service].js b/src/pages/api/docker/status/[...service].js index d59fffd7..2aafe687 100644 --- a/src/pages/api/docker/status/[...service].js +++ b/src/pages/api/docker/status/[...service].js @@ -1,4 +1,5 @@ import Docker from "dockerode"; + import getDockerArguments from "utils/docker"; export default async function handler(req, res) { @@ -25,9 +26,7 @@ export default async function handler(req, res) { }); } - const containerNames = containers.map((container) => { - return container.Names[0].replace(/^\//, ""); - }); + const containerNames = containers.map((container) => container.Names[0].replace(/^\//, "")); const containerExists = containerNames.includes(containerName); if (!containerExists) { diff --git a/src/pages/api/proxy.js b/src/pages/api/proxy.js index 1a8b1811..ff7ca357 100644 --- a/src/pages/api/proxy.js +++ b/src/pages/api/proxy.js @@ -1,4 +1,5 @@ import https from "https"; + import getRawBody from "raw-body"; import { httpRequest, httpsRequest } from "utils/http"; @@ -11,7 +12,8 @@ export const config = { export default async function handler(req, res) { const headers = ["X-API-Key", "Authorization"].reduce((obj, key) => { - if (req.headers && req.headers.hasOwnProperty(key.toLowerCase())) { + if (req.headers && Object.prototype.hasOwnProperty.call(req.headers, key.toLowerCase())) { + // eslint-disable-next-line no-param-reassign obj[key] = req.headers[key.toLowerCase()]; } return obj; @@ -29,23 +31,9 @@ export default async function handler(req, res) { const [status, contentType, data] = await httpsRequest(url, { agent: httpsAgent, method: req.method, - headers: headers, + headers, body: - req.method == "GET" || req.method == "HEAD" - ? null - : await getRawBody(req, { - encoding: "utf8", - }), - }); - - res.setHeader("Content-Type", contentType); - return res.status(status).send(data); - } else { - const [status, contentType, data] = await httpRequest(url, { - method: req.method, - headers: headers, - body: - req.method == "GET" || req.method == "HEAD" + req.method === "GET" || req.method === "HEAD" ? null : await getRawBody(req, { encoding: "utf8", @@ -55,4 +43,17 @@ export default async function handler(req, res) { res.setHeader("Content-Type", contentType); return res.status(status).send(data); } + const [status, contentType, data] = await httpRequest(url, { + method: req.method, + headers, + body: + req.method === "GET" || req.method === "HEAD" + ? null + : await getRawBody(req, { + encoding: "utf8", + }), + }); + + res.setHeader("Content-Type", contentType); + return res.status(status).send(data); } diff --git a/src/pages/api/services/index.js b/src/pages/api/services/index.js index 572ab404..40f1ea69 100644 --- a/src/pages/api/services/index.js +++ b/src/pages/api/services/index.js @@ -1,6 +1,8 @@ import { promises as fs } from "fs"; import path from "path"; + import yaml from "js-yaml"; + import checkAndCopyConfig from "utils/config"; export default async function handler(req, res) { @@ -11,30 +13,28 @@ export default async function handler(req, res) { const services = yaml.load(fileContents); // map easy to write YAML objects into easy to consume JS arrays - const servicesArray = services.map((group) => { - return { - name: Object.keys(group)[0], - services: group[Object.keys(group)[0]].map((entries) => { - const { widget, ...service } = entries[Object.keys(entries)[0]]; - let res = { - name: Object.keys(entries)[0], - ...service, + const servicesArray = services.map((group) => ({ + name: Object.keys(group)[0], + services: group[Object.keys(group)[0]].map((entries) => { + const { widget, ...service } = entries[Object.keys(entries)[0]]; + const result = { + name: Object.keys(entries)[0], + ...service, + }; + + if (widget) { + const { type } = widget; + + result.widget = { + type, + service_group: Object.keys(group)[0], + service_name: Object.keys(entries)[0], }; + } - if (widget) { - const { type } = widget; - - res.widget = { - type: type, - service_group: Object.keys(group)[0], - service_name: Object.keys(entries)[0], - }; - } - - return res; - }), - }; - }); + return result; + }), + })); res.send(servicesArray); } diff --git a/src/pages/api/services/proxy.js b/src/pages/api/services/proxy.js index 63e047bc..970008e1 100644 --- a/src/pages/api/services/proxy.js +++ b/src/pages/api/services/proxy.js @@ -33,5 +33,5 @@ export default async function handler(req, res) { return serviceProxyHandler(req, res); } - res.status(403).json({ error: "Unkown proxy service type" }); + return res.status(403).json({ error: "Unkown proxy service type" }); } diff --git a/src/pages/api/widgets/index.js b/src/pages/api/widgets/index.js index 98496ad6..3e96c26b 100644 --- a/src/pages/api/widgets/index.js +++ b/src/pages/api/widgets/index.js @@ -1,6 +1,8 @@ import { promises as fs } from "fs"; import path from "path"; + import yaml from "js-yaml"; + import checkAndCopyConfig from "utils/config"; export default async function handler(req, res) { @@ -11,12 +13,10 @@ export default async function handler(req, res) { const widgets = yaml.load(fileContents); // map easy to write YAML objects into easy to consume JS arrays - const widgetsArray = widgets.map((group) => { - return { - type: Object.keys(group)[0], - options: { ...group[Object.keys(group)[0]] }, - }; - }); + const widgetsArray = widgets.map((group) => ({ + type: Object.keys(group)[0], + options: { ...group[Object.keys(group)[0]] }, + })); res.send(widgetsArray); } diff --git a/src/pages/api/widgets/openweathermap.js b/src/pages/api/widgets/openweathermap.js index 5a687725..1bda5ebf 100644 --- a/src/pages/api/widgets/openweathermap.js +++ b/src/pages/api/widgets/openweathermap.js @@ -22,7 +22,7 @@ export default async function handler(req, res) { return res.status(400).json({ error: "Missing API key" }); } - const api_url = `https://api.openweathermap.org/data/2.5/weather?lat=${latitude}&lon=${longitude}&appid=${apiKey}&units=${units}`; + const apiUrl = `https://api.openweathermap.org/data/2.5/weather?lat=${latitude}&lon=${longitude}&appid=${apiKey}&units=${units}`; - res.send(await cachedFetch(api_url, cache)); + return res.send(await cachedFetch(apiUrl, cache)); } diff --git a/src/pages/api/widgets/resources.js b/src/pages/api/widgets/resources.js index f9e51b19..ceb3ff53 100644 --- a/src/pages/api/widgets/resources.js +++ b/src/pages/api/widgets/resources.js @@ -1,26 +1,30 @@ -import { cpu, drive, mem, netstat } from "node-os-utils"; +import { cpu, drive, mem } from "node-os-utils"; export default async function handler(req, res) { const { type, target } = req.query; - if (type == "cpu") { + if (type === "cpu") { return res.status(200).json({ cpu: { usage: await cpu.usage(1000), load: cpu.loadavgTime(5), }, }); - } else if (type == "disk") { + } + + if (type === "disk") { return res.status(200).json({ drive: await drive.info(target || "/"), }); - } else if (type == "memory") { + } + + if (type === "memory") { return res.status(200).json({ memory: await mem.info(), }); - } else { - return res.status(400).json({ - error: "invalid type", - }); } + + return res.status(400).json({ + error: "invalid type", + }); } diff --git a/src/pages/api/widgets/weather.js b/src/pages/api/widgets/weather.js index a7da1093..2cda1c04 100644 --- a/src/pages/api/widgets/weather.js +++ b/src/pages/api/widgets/weather.js @@ -22,7 +22,7 @@ export default async function handler(req, res) { return res.status(400).json({ error: "Missing API key" }); } - const api_url = `http://api.weatherapi.com/v1/current.json?q=${latitude},${longitude}&key=${apiKey}`; + const apiUrl = `http://api.weatherapi.com/v1/current.json?q=${latitude},${longitude}&key=${apiKey}`; - res.send(await cachedFetch(api_url, cache)); + return res.send(await cachedFetch(apiUrl, cache)); } diff --git a/src/pages/index.js b/src/pages/index.jsx similarity index 81% rename from src/pages/index.js rename to src/pages/index.jsx index 009ee00e..c0121a55 100644 --- a/src/pages/index.js +++ b/src/pages/index.jsx @@ -2,13 +2,11 @@ import useSWR from "swr"; import Head from "next/head"; import dynamic from "next/dynamic"; -import { ThemeProvider } from "utils/theme-context"; - import ServicesGroup from "components/services/group"; import BookmarksGroup from "components/bookmarks/group"; import Widget from "components/widget"; import { ColorProvider } from "utils/color-context"; -import Search from "components/widgets/search/search"; +import { ThemeProvider } from "utils/theme-context"; const ThemeToggle = dynamic(() => import("components/theme-toggle"), { ssr: false, @@ -19,12 +17,11 @@ const ColorToggle = dynamic(() => import("components/color-toggle"), { }); const rightAlignedWidgets = ["weatherapi", "openweathermap", "weather", "search"]; -const expandedWidgets = ["search"]; export default function Home() { - const { data: services, error: servicesError } = useSWR("/api/services"); - const { data: bookmarks, error: bookmarksError } = useSWR("/api/bookmarks"); - const { data: widgets, error: widgetsError } = useSWR("/api/widgets"); + const { data: services } = useSWR("/api/services"); + const { data: bookmarks } = useSWR("/api/bookmarks"); + const { data: widgets } = useSWR("/api/widgets"); return ( <ColorProvider> @@ -38,15 +35,15 @@ export default function Home() { <> {widgets .filter((widget) => !rightAlignedWidgets.includes(widget.type)) - .map((widget, i) => ( - <Widget key={i} widget={widget} /> + .map((widget) => ( + <Widget key={widget} widget={widget} /> ))} <div className="flex flex-wrap basis-full space-x-0 sm:space-x-4 grow sm:basis-auto justify-between md:justify-end mt-2 md:mt-0"> {widgets .filter((widget) => rightAlignedWidgets.includes(widget.type)) - .map((widget, i) => ( - <Widget key={i} widget={widget} /> + .map((widget) => ( + <Widget key={widget} widget={widget} /> ))} </div> </> diff --git a/src/utils/api-helpers.js b/src/utils/api-helpers.js index 1cbefab8..a889487e 100644 --- a/src/utils/api-helpers.js +++ b/src/utils/api-helpers.js @@ -15,13 +15,13 @@ const formats = { }; export function formatApiCall(api, args) { - const match = /\{.*?\}/g; + const find = /\{.*?\}/g; const replace = (match) => { const key = match.replace(/\{|\}/g, ""); return args[key]; }; - return formats[api].replace(match, replace); + return formats[api].replace(find, replace); } export function formatApiUrl(widget, endpoint) { diff --git a/src/utils/cached-fetch.js b/src/utils/cached-fetch.js index 88372d53..22eba37f 100644 --- a/src/utils/cached-fetch.js +++ b/src/utils/cached-fetch.js @@ -5,9 +5,9 @@ export default async function cachedFetch(url, duration) { if (cached) { return cached; - } else { - const data = await fetch(url).then((res) => res.json()); - cache.put(url, data, duration * 1000 * 60); - return data; } + + const data = await fetch(url).then((res) => res.json()); + cache.put(url, data, duration * 1000 * 60); + return data; } diff --git a/src/utils/color-context.js b/src/utils/color-context.jsx similarity index 75% rename from src/utils/color-context.js rename to src/utils/color-context.jsx index 08063b23..83aa692d 100644 --- a/src/utils/color-context.js +++ b/src/utils/color-context.jsx @@ -1,4 +1,4 @@ -import { createContext, useState, useEffect } from "react"; +import { createContext, useState, useEffect, useMemo } from "react"; let lastColor = false; @@ -16,7 +16,7 @@ const getInitialColor = () => { export const ColorContext = createContext(); -export const ColorProvider = ({ initialTheme, children }) => { +export function ColorProvider({ initialTheme, children }) { const [color, setColor] = useState(getInitialColor); const rawSetColor = (rawColor) => { @@ -38,5 +38,7 @@ export const ColorProvider = ({ initialTheme, children }) => { rawSetColor(color); }, [color]); - return <ColorContext.Provider value={{ color, setColor }}>{children}</ColorContext.Provider>; -}; + const value = useMemo(() => ({ color, setColor }), [color]); + + return <ColorContext.Provider value={value}>{children}</ColorContext.Provider>; +} diff --git a/src/utils/condition-map.js b/src/utils/condition-map.js index bf99027e..f00b109c 100644 --- a/src/utils/condition-map.js +++ b/src/utils/condition-map.js @@ -348,7 +348,9 @@ export default function mapIcon(weatherStatusCode, timeOfDay) { if (mapping) { if (timeOfDay === "day") { return mapping.icon.day; - } else if (timeOfDay === "night") { + } + + if (timeOfDay === "night") { return mapping.icon.night; } } diff --git a/src/utils/config.js b/src/utils/config.js index 5fa3fa7f..7b70de37 100644 --- a/src/utils/config.js +++ b/src/utils/config.js @@ -1,5 +1,7 @@ +/* eslint-disable no-console */ import { join } from "path"; import { existsSync, copyFile, promises as fs } from "fs"; + import yaml from "js-yaml"; export default function checkAndCopyConfig(config) { @@ -8,7 +10,7 @@ export default function checkAndCopyConfig(config) { const configSkeleton = join(process.cwd(), "src", "skeleton", config); copyFile(configSkeleton, configYaml, (err) => { if (err) { - console.log("error copying config", err); + console.error("error copying config", err); throw err; } console.info("%s was copied to the config folder", config); diff --git a/src/utils/docker.js b/src/utils/docker.js index 9d2d4646..0bc6b843 100644 --- a/src/utils/docker.js +++ b/src/utils/docker.js @@ -1,6 +1,8 @@ -import yaml from "js-yaml"; import path from "path"; import { promises as fs } from "fs"; + +import yaml from "js-yaml"; + import checkAndCopyConfig from "utils/config"; export default async function getDockerArguments(server) { @@ -13,18 +15,21 @@ export default async function getDockerArguments(server) { if (!server) { if (process.platform !== "win32" && process.platform !== "darwin") { return { socketPath: "/var/run/docker.sock" }; - } else { - return { host: "127.0.0.1" }; } - } else if (servers[server]) { + + return { host: "127.0.0.1" }; + } + + if (servers[server]) { if (servers[server].socket) { return { socketPath: servers[server].socket }; - } else if (servers[server].host) { - return { host: servers[server].host, port: servers[server].port || null }; - } else { - return servers[server]; } - } else { - return null; + + if (servers[server].host) { + return { host: servers[server].host, port: servers[server].port || null }; + } + + return servers[server]; } + return null; } diff --git a/src/utils/http.js b/src/utils/http.js index 91c8c5fb..5f32f1a1 100644 --- a/src/utils/http.js +++ b/src/utils/http.js @@ -1,10 +1,11 @@ +/* eslint-disable prefer-promise-reject-errors */ import https from "https"; import http from "http"; export function httpsRequest(url, params) { - return new Promise(function (resolve, reject) { - var request = https.request(url, params, function (response) { - var data = []; + return new Promise((resolve, reject) => { + const request = https.request(url, params, (response) => { + const data = []; response.on("data", (chunk) => { data.push(chunk); @@ -24,9 +25,9 @@ export function httpsRequest(url, params) { } export function httpRequest(url, params) { - return new Promise(function (resolve, reject) { - var request = http.request(url, params, function (response) { - var data = []; + return new Promise((resolve, reject) => { + const request = http.request(url, params, (response) => { + const data = []; response.on("data", (chunk) => { data.push(chunk); @@ -57,7 +58,6 @@ export function httpProxy(url, params = {}) { agent: httpsAgent, ...params, }); - } else { - return httpRequest(constructedUrl, params); } + return httpRequest(constructedUrl, params); } diff --git a/src/utils/owm-condition-map.js b/src/utils/owm-condition-map.js index ab5658f2..79ca32f7 100644 --- a/src/utils/owm-condition-map.js +++ b/src/utils/owm-condition-map.js @@ -135,7 +135,7 @@ const conditions = [ night: Icons.WiNightAltShowers, }, }, - + { code: 500, icon: { @@ -393,18 +393,17 @@ const conditions = [ night: Icons.WiCloudy, }, }, - ]; export default function mapIcon(weatherStatusCode, timeOfDay) { - const mapping = conditions.find( - (condition) => condition.code === weatherStatusCode - ); + const mapping = conditions.find((condition) => condition.code === weatherStatusCode); if (mapping) { if (timeOfDay === "day") { return mapping.icon.day; - } else if (timeOfDay === "night") { + } + + if (timeOfDay === "night") { return mapping.icon.night; } } diff --git a/src/utils/proxies/credentialed.js b/src/utils/proxies/credentialed.js index 9672a759..820d8cdb 100644 --- a/src/utils/proxies/credentialed.js +++ b/src/utils/proxies/credentialed.js @@ -1,4 +1,4 @@ -import { getServiceWidget } from "utils/service-helpers"; +import getServiceWidget from "utils/service-helpers"; import { formatApiCall } from "utils/api-helpers"; import { httpProxy } from "utils/http"; diff --git a/src/utils/proxies/generic.js b/src/utils/proxies/generic.js index 09eb9a0e..417d0070 100644 --- a/src/utils/proxies/generic.js +++ b/src/utils/proxies/generic.js @@ -1,4 +1,4 @@ -import { getServiceWidget } from "utils/service-helpers"; +import getServiceWidget from "utils/service-helpers"; import { formatApiCall } from "utils/api-helpers"; import { httpProxy } from "utils/http"; diff --git a/src/utils/proxies/npm.js b/src/utils/proxies/npm.js index d6c68cd8..d60cedd1 100644 --- a/src/utils/proxies/npm.js +++ b/src/utils/proxies/npm.js @@ -1,4 +1,4 @@ -import { getServiceWidget } from "utils/service-helpers"; +import getServiceWidget from "utils/service-helpers"; import { formatApiCall } from "utils/api-helpers"; export default async function npmProxyHandler(req, res) { @@ -25,7 +25,7 @@ export default async function npmProxyHandler(req, res) { method: "GET", headers: { "Content-Type": "application/json", - Authorization: "Bearer " + authResponse.token, + Authorization: `Bearer ${authResponse.token}`, }, }).then((response) => response.json()); diff --git a/src/utils/proxies/nzbget.js b/src/utils/proxies/nzbget.js index 7bf078b4..7f0a450d 100644 --- a/src/utils/proxies/nzbget.js +++ b/src/utils/proxies/nzbget.js @@ -1,5 +1,6 @@ import { JSONRPCClient } from "json-rpc-2.0"; -import { getServiceWidget } from "utils/service-helpers"; + +import getServiceWidget from "utils/service-helpers"; export default async function nzbgetProxyHandler(req, res) { const { group, service, endpoint } = req.query; @@ -25,9 +26,9 @@ export default async function nzbgetProxyHandler(req, res) { if (response.status === 200) { const jsonRPCResponse = await response.json(); return client.receive(jsonRPCResponse); - } else if (jsonRPCRequest.id !== undefined) { - return Promise.reject(new Error(response.statusText)); } + + return Promise.reject(new Error(response.statusText)); }) ); diff --git a/src/utils/proxies/rutorrent.js b/src/utils/proxies/rutorrent.js index 3f9dda3c..78e7b815 100644 --- a/src/utils/proxies/rutorrent.js +++ b/src/utils/proxies/rutorrent.js @@ -1,6 +1,6 @@ import RuTorrent from "rutorrent-promise"; -import { getServiceWidget } from "utils/service-helpers"; +import getServiceWidget from "utils/service-helpers"; export default async function rutorrentProxyHandler(req, res) { const { group, service } = req.query; diff --git a/src/utils/service-helpers.js b/src/utils/service-helpers.js index 986fc227..f17434f4 100644 --- a/src/utils/service-helpers.js +++ b/src/utils/service-helpers.js @@ -1,24 +1,21 @@ import { promises as fs } from "fs"; import path from "path"; + import yaml from "js-yaml"; -export async function getServiceWidget(group, service) { +export default async function getServiceWidget(group, service) { const servicesYaml = path.join(process.cwd(), "config", "services.yaml"); const fileContents = await fs.readFile(servicesYaml, "utf8"); const services = yaml.load(fileContents); // map easy to write YAML objects into easy to consume JS arrays - const servicesArray = services.map((group) => { - return { - name: Object.keys(group)[0], - services: group[Object.keys(group)[0]].map((entries) => { - return { - name: Object.keys(entries)[0], - ...entries[Object.keys(entries)[0]], - }; - }), - }; - }); + const servicesArray = services.map((servicesGroup) => ({ + name: Object.keys(servicesGroup)[0], + services: servicesGroup[Object.keys(servicesGroup)[0]].map((entries) => ({ + name: Object.keys(entries)[0], + ...entries[Object.keys(entries)[0]], + })), + })); const serviceGroup = servicesArray.find((g) => g.name === group); if (serviceGroup) { diff --git a/src/utils/stats-helpers.js b/src/utils/stats-helpers.js index b98f1cd7..6d4ee7ad 100644 --- a/src/utils/stats-helpers.js +++ b/src/utils/stats-helpers.js @@ -19,7 +19,7 @@ export function formatBytes(bytes, decimals = 2) { const i = Math.floor(Math.log(bytes) / Math.log(k)); - return parseFloat(bytes / Math.pow(k, i)).toFixed(dm) + " " + sizes[i]; + return `${parseFloat(bytes / k ** i).toFixed(dm)} ${sizes[i]}`; } export function formatBits(bytes, decimals = 2) { @@ -31,5 +31,5 @@ export function formatBits(bytes, decimals = 2) { const i = Math.floor(Math.log(bytes) / Math.log(k)); - return parseFloat(bytes / Math.pow(k, i)).toFixed(dm) + " " + sizes[i]; + return `${parseFloat(bytes / k ** i).toFixed(dm)} ${sizes[i]}`; } diff --git a/src/utils/theme-context.js b/src/utils/theme-context.jsx similarity index 76% rename from src/utils/theme-context.js rename to src/utils/theme-context.jsx index 476fcb8d..89b5a57b 100644 --- a/src/utils/theme-context.js +++ b/src/utils/theme-context.jsx @@ -1,4 +1,4 @@ -import { createContext, useState, useEffect } from "react"; +import { createContext, useState, useEffect, useMemo } from "react"; const getInitialTheme = () => { if (typeof window !== "undefined" && window.localStorage) { @@ -18,7 +18,7 @@ const getInitialTheme = () => { export const ThemeContext = createContext(); -export const ThemeProvider = ({ initialTheme, children }) => { +export function ThemeProvider({ initialTheme, children }) { const [theme, setTheme] = useState(getInitialTheme); const rawSetTheme = (rawTheme) => { @@ -39,5 +39,7 @@ export const ThemeProvider = ({ initialTheme, children }) => { rawSetTheme(theme); }, [theme]); - return <ThemeContext.Provider value={{ theme, setTheme }}>{children}</ThemeContext.Provider>; -}; + const value = useMemo(() => ({ theme, setTheme }), [theme]); + + return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>; +}