diff --git a/.dockerignore b/.dockerignore index 125cd198c..13609ca43 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,5 +2,8 @@ Dockerfile .dockerignore node_modules npm-debug.log -README.md +*.md .git +.github +LICENSE +docs/ diff --git a/.eslintrc.js b/.eslintrc.js index abde8f7cb..16b883880 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -3,7 +3,6 @@ module.exports = { 'mantine', 'plugin:@next/next/recommended', 'plugin:jest/recommended', - 'plugin:storybook/recommended', "eslint:recommended", "plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/recommended" 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/.github/workflows/docker.yml b/.github/workflows/docker.yml index 2c8c0e1dd..40b6216ae 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -1,6 +1,8 @@ -name: Master docker CI -# Workflow to build and publish docker image - +name: Master CI +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. on: push: branches: [master] @@ -22,72 +24,46 @@ jobs: # Push image to GitHub Packages. # See also https://docs.docker.com/docker-hub/builds/ yarn_install_and_build: - # Will run yarn install && yarn build - runs-on: ubuntu-latest - steps: - - name: Setup - uses: actions/setup-node@v3 - - name: Checkout - uses: actions/checkout@v3 - - name: Get yarn cache directory path - # to help speed up build times - id: yarn-cache-dir-path - run: echo "::set-output name=dir::$(yarn cache dir)" - - name: Yarn cache - # to help speed up build times - uses: actions/cache@v3 - id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) - with: - path: ${{ steps.yarn-cache-dir-path.outputs.dir }} - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: ${{ runner.os }}-yarn- - - name: Nextjs cache - uses: actions/cache@v2 - with: - # See here for caching with `yarn` https://github.com/actions/cache/blob/main/examples.md#node---yarn or you can leverage caching with actions/setup-node https://github.com/actions/setup-node - path: | - ~/.npm - ${{ github.workspace }}/.next/cache - # Generate a new cache whenever packages or source files change. - key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }} - # If source files changed but packages didn't, rebuild from a prior cache. - restore-keys: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}- - - run: yarn install --immutable - - run: yarn build - - name: Cache build output - # to copy needed files to docker build job - uses: actions/cache@v2 - id: restore-build - with: - path: | - ./next.config.js - ./pages/ - ./public/ - ./.next/static/ - ./.next/standalone/ - ./packages.json - key: ${{ github.sha }} - - docker_image_build_and_push: - needs: [yarn_install_and_build] runs-on: ubuntu-latest permissions: packages: write contents: read steps: + + - name: Setup + uses: actions/setup-node@v3 + - name: Checkout - uses: actions/checkout@v2 - - uses: actions/cache@v2 - id: restore-build + uses: actions/checkout@v3 + + - name: Get yarn cache directory path + id: yarn-cache-dir-path + run: echo "::set-output name=dir::$(yarn config get cacheFolder)" + + - uses: actions/cache@v3 + id: yarn-cache with: + path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + + - name: Restore NextJS cache + uses: actions/cache@v2 + with: + # See here for caching with `yarn` https://github.com/actions/cache/blob/main/examples.md#node---yarn or you can leverage caching with actions/setup-node https://github.com/actions/setup-node path: | - ./next.config.js - ./pages/ - ./public/ - ./.next/static/ - ./.next/standalone/ - ./packages.json - key: ${{ github.sha }} + ${{ github.workspace }}/.next/cache + # Generate a new cache whenever packages or source files change. + key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }} + # If source files changed but packages didn't, rebuild from a prior cache. + restore-keys: | + ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}- + + - run: yarn install --immutable + + - run: yarn build + - name: Docker meta id: meta uses: docker/metadata-action@v4 @@ -98,10 +74,13 @@ jobs: tags: | type=raw,value=latest type=pep440,pattern={{version}} + - name: Set up QEMU uses: docker/setup-qemu-action@v2 + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 + - name: Login to GHCR uses: docker/login-action@v2 with: @@ -117,3 +96,5 @@ jobs: push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/docker_dev.yml b/.github/workflows/docker_dev.yml index 3a43a5ab6..05ba6d6d6 100644 --- a/.github/workflows/docker_dev.yml +++ b/.github/workflows/docker_dev.yml @@ -30,6 +30,9 @@ jobs: # See also https://docs.docker.com/docker-hub/builds/ yarn_install_and_build: runs-on: ubuntu-latest + permissions: + packages: write + contents: read steps: - name: Setup @@ -40,67 +43,32 @@ jobs: - name: Get yarn cache directory path id: yarn-cache-dir-path - run: echo "::set-output name=dir::$(yarn cache dir)" - - - name: Yarn cache - uses: actions/cache@v3 - id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) + run: echo "::set-output name=dir::$(yarn config get cacheFolder)" + + - uses: actions/cache@v3 + id: yarn-cache with: path: ${{ steps.yarn-cache-dir-path.outputs.dir }} key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: ${{ runner.os }}-yarn- + restore-keys: | + ${{ runner.os }}-yarn- - - name: Nextjs cache + - name: Restore NextJS cache uses: actions/cache@v2 with: - # See here for caching with `yarn` https://github.com/actions/cache/blob/main/examples.md#node---yarn or you can leverage caching with actions/setup-node https://github.com/actions/setup-node + # See here for caching with `yarn` https://github.com/actions/cache/blob/main/examples.md#node---yarn or you can leverage caching with actions/setup-node https://github.com/actions/setup-node path: | - ~/.npm ${{ github.workspace }}/.next/cache # Generate a new cache whenever packages or source files change. key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }} # If source files changed but packages didn't, rebuild from a prior cache. - restore-keys: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}- + restore-keys: | + ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}- - run: yarn install --immutable + - run: yarn build - - name: Cache build output - uses: actions/cache@v2 - id: restore-build - with: - path: | - ./next.config.js - ./pages/ - ./public/ - ./.next/static/ - ./.next/standalone/ - ./packages.json - key: ${{ github.sha }} - - docker_image_build_and_push: - needs: [yarn_install_and_build] - runs-on: ubuntu-latest - permissions: - packages: write - contents: read - steps: - - - name: Checkout - uses: actions/checkout@v2 - - - uses: actions/cache@v2 - id: restore-build - with: - path: | - ./next.config.js - ./pages/ - ./public/ - ./.next/static/ - ./.next/standalone/ - ./packages.json - key: ${{ github.sha }} - - name: Docker meta id: meta uses: docker/metadata-action@v4 @@ -134,3 +102,5 @@ jobs: push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.storybook/main.js b/.storybook/main.js deleted file mode 100644 index e0e85fcfa..000000000 --- a/.storybook/main.js +++ /dev/null @@ -1,30 +0,0 @@ -module.exports = { - stories: ['../src/components/**/*.story.mdx', '../src/components/**/*.story.*'], - addons: [ - '@storybook/addon-links', - 'storybook-addon-mock/register', - '@storybook/addon-essentials', - ], - typescript: { - check: false, - reactDocgen: false, - }, - framework: '@storybook/react', - features: { emotionAlias: false }, - webpackFinal: async (config, { configType }) => { - // `configType` has a value of 'DEVELOPMENT' or 'PRODUCTION' - // You can change the configuration based on that. - // 'PRODUCTION' is used when building the static version of storybook. - - // https://github.com/polkadot-js/extension/issues/621#issuecomment-759341776 - // framer-motion uses the .mjs notation and we need to include it so that webpack will - // transpile it for us correctly (enables using a CJS module inside an ESM). - config.module.rules.push({ - test: /\.mjs$/, - include: /node_modules/, - type: 'javascript/auto', - }); - // Return the altered config - return config; - }, -}; diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx deleted file mode 100644 index 5023bb98f..000000000 --- a/.storybook/preview.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { MantineProvider, ColorSchemeProvider } from '@mantine/core'; -import { NotificationsProvider } from '@mantine/notifications'; - -export const parameters = { layout: 'fullscreen' }; - -function ThemeWrapper(props: { children: React.ReactNode }) { - return ( - {}}> - - {props.children} - - - ); -} - -export const decorators = [(renderStory: Function) => {renderStory()}]; 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/Dockerfile b/Dockerfile index 39faff156..9d2244dd1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,14 +1,23 @@ FROM node:16-alpine WORKDIR /app -ENV NODE_ENV production -COPY /next.config.js ./ -COPY /public ./public -COPY /package.json ./package.json -# Automatically leverage output traces to reduce image size. https://nextjs.org/docs/advanced-features/output-file-tracing -COPY /.next/standalone ./ -COPY /.next/static ./.next/static -EXPOSE 7575 -ENV PORT 7575 + RUN apk add tzdata -VOLUME /app/data/configs + +ENV NEXT_TELEMETRY_DISABLED 1 + +ENV NODE_ENV production + +COPY next.config.js ./ +COPY public ./public +COPY package.json ./package.json + +# Automatically leverage output traces to reduce image size +# https://nextjs.org/docs/advanced-features/output-file-tracing +COPY .next/standalone ./ +COPY .next/static ./.next/static + +EXPOSE 7575 + +ENV PORT 7575 + CMD ["node", "server.js"] 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 2eadce9ae..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.7.1'; +export const CURRENT_VERSION = 'v0.8.2'; diff --git a/next.config.js b/next.config.js index 31fc7b641..7344769da 100644 --- a/next.config.js +++ b/next.config.js @@ -6,11 +6,5 @@ const withBundleAnalyzer = require('@next/bundle-analyzer')({ module.exports = withBundleAnalyzer({ reactStrictMode: false, - eslint: { - ignoreDuringBuilds: true, - }, - experimental: { - outputStandalone: true, - }, - basePath: env.BASE_URL, + output: 'standalone', }); diff --git a/package.json b/package.json index 1d407ac5f..f4d54ca2b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,8 @@ { "name": "homarr", - "version": "0.7.1", + "version": "0.8.2", "description": "Homarr - A homepage for your server.", + "license": "MIT", "repository": { "type": "git", "url": "https://github.com/ajnart/homarr" @@ -19,70 +20,63 @@ "prettier:check": "prettier --check \"**/*.{ts,tsx}\"", "prettier:write": "prettier --write \"**/*.{ts,tsx}\"", "test": "npm run prettier:check && npm run lint && npm run typecheck && npm run jest", - "storybook": "start-storybook -p 7001", - "storybook:build": "build-storybook", "ci": "yarn test && yarn lint --fix && yarn typecheck && yarn prettier:write" }, "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", "axios": "^0.27.2", - "cookies-next": "^2.0.4", - "dayjs": "^1.11.3", - "framer-motion": "^6.3.1", + "cookies-next": "^2.1.1", + "dayjs": "^1.11.4", + "dockerode": "^3.3.2", + "framer-motion": "^6.5.1", "js-file-download": "^0.4.12", - "next": "12.1.6", - "prism-react-renderer": "^1.3.1", - "react": "^17.0.1", - "react-dom": "^17.0.1", - "systeminformation": "^5.11.16", + "next": "12.2.0", + "prism-react-renderer": "^1.3.5", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "systeminformation": "^5.12.1", "uuid": "^8.3.2" }, "devDependencies": { - "@babel/core": "^7.17.8", - "@next/bundle-analyzer": "^12.1.4", - "@next/eslint-plugin-next": "^12.1.4", - "@storybook/react": "^6.5.4", - "@types/node": "^17.0.23", - "@types/react": "17.0.43", + "@next/bundle-analyzer": "12.2.0", + "@next/eslint-plugin-next": "12.2.0", + "@types/dockerode": "^3.3.9", + "@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-testing-library": "^5.5.1", "eslint-plugin-unused-imports": "^2.0.0", - "jest": "^28.1.0", - "prettier": "^2.6.2", - "require-from-string": "^2.0.2", - "typescript": "4.6.4" - }, - "resolutions": { - "@types/react": "17.0.30" + "jest": "^28.1.3", + "prettier": "^2.7.1", + "typescript": "^4.7.4" }, "packageManager": "yarn@3.2.1" } diff --git a/src/components/AppShelf/AddAppShelfItem.tsx b/src/components/AppShelf/AddAppShelfItem.tsx index f9229d220..2d41feaee 100644 --- a/src/components/AppShelf/AddAppShelfItem.tsx +++ b/src/components/AppShelf/AddAppShelfItem.tsx @@ -1,29 +1,29 @@ import { - Modal, + ActionIcon, + Anchor, + Button, Center, Group, - TextInput, Image, - Button, - Select, LoadingOverlay, - ActionIcon, - Tooltip, - Title, - Anchor, - Text, - Tabs, + Modal, MultiSelect, ScrollArea, + Select, Switch, + Tabs, + TextInput, + Title, + Tooltip, } from '@mantine/core'; import { useForm } from '@mantine/form'; -import { useEffect, useState } from 'react'; -import { IconApps as Apps } from '@tabler/icons'; -import { v4 as uuidv4 } from 'uuid'; import { useDebouncedValue } from '@mantine/hooks'; +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) { const [opened, setOpened] = useState(false); @@ -54,11 +54,13 @@ 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, '-') - .toLowerCase()}.png` + .toLowerCase() + .replace(/^dash\.$/, 'dashdot')}.png` ).then((res) => { if (res.ok) { form.setFieldValue('icon', res.url); @@ -75,22 +77,7 @@ 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: '8686' }, - { name: 'deluge', value: '8112' }, - { name: 'transmission', value: '9091' }, - ]; - // 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) { const { setOpened } = props; @@ -111,7 +98,7 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } & type: props.type ?? 'Other', category: props.category ?? undefined, name: props.name ?? '', - icon: props.icon ?? '/favicon.svg', + icon: props.icon ?? DEFAULT_ICON, url: props.url ?? '', apiKey: props.apiKey ?? (undefined as unknown as string), username: props.username ?? (undefined as unknown as string), @@ -123,13 +110,9 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } & validate: { apiKey: () => null, // Validate icon with a regex - icon: (value: string) => { - // Regex to match everything that ends with and icon extension - if (!value.match(/\.(png|jpg|jpeg|gif|svg)$/)) { - return 'Please enter a valid icon URL'; - } - return null; - }, + icon: (value: string) => + // Disable matching to allow any values + null, // Validate url with a regex http/https url: (value: string) => { try { @@ -150,10 +133,10 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } & const [debounced, cancel] = useDebouncedValue(form.values.name, 250); useEffect(() => { - if (form.values.name !== debounced || props.name || props.type) return; + if (form.values.name !== debounced || form.values.icon !== DEFAULT_ICON) return; MatchIcon(form.values.name, form); 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) @@ -223,7 +206,7 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } & void } & }} error={form.errors.apiKey && 'Invalid API key'} /> - - Tip: Get your API key{' '} + + Get your API key{' '} void } & > here. - + )} {form.values.type === 'qBittorrent' && ( @@ -321,9 +297,20 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } & /> )} - {(form.values.type === 'Deluge' || - form.values.type === 'Transmission' || - form.values.type === 'qBittorrent') && ( + {form.values.type === 'Deluge' && ( + <> + { + form.setFieldValue('password', event.currentTarget.value); + }} + error={form.errors.password && 'Invalid password'} + /> + + )} + {form.values.type === 'Transmission' && ( <> void } & /> { form.setFieldValue('password', event.currentTarget.value); diff --git a/src/components/AppShelf/AppShelf.story.tsx b/src/components/AppShelf/AppShelf.story.tsx deleted file mode 100644 index c73a42f19..000000000 --- a/src/components/AppShelf/AppShelf.story.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { SimpleGrid } from '@mantine/core'; -import AppShelf from './AppShelf'; -import { AppShelfItem } from './AppShelfItem'; - -export default { - title: 'Item Shelf', - component: AppShelf, - args: { - service: { - name: 'qBittorrent', - url: 'http://', - icon: 'https://cdn.jsdelivr.net/gh/IceWhaleTech/CasaOS-AppStore@main/Apps/qBittorrent/icon.png', - type: 'qBittorrent', - apiKey: '', - }, - }, -}; - -export const Default = (args: any) => ; -export const One = (args: any) => ; -export const Ten = (args: any) => ( - - {Array.from(Array(10)).map((_, i) => ( - - ))} - -); diff --git a/src/components/AppShelf/AppShelf.tsx b/src/components/AppShelf/AppShelf.tsx index 05c0d1ada..a47346830 100644 --- a/src/components/AppShelf/AppShelf.tsx +++ b/src/components/AppShelf/AppShelf.tsx @@ -20,15 +20,30 @@ import DownloadComponent from '../modules/downloads/DownloadsModule'; const useStyles = createStyles((theme, _params) => ({ item: { - borderBottom: 0, overflow: 'hidden', - border: '1px solid transparent', - borderRadius: theme.radius.lg, + borderLeft: '3px solid transparent', + borderRight: '3px solid transparent', + borderBottom: '3px solid transparent', + borderRadius: '20px', + borderColor: theme.colorScheme === 'dark' ? theme.colors.dark[5] : theme.colors.gray[1], marginTop: theme.spacing.md, }, - itemOpened: { - borderColor: theme.colorScheme === 'dark' ? theme.colors.dark[5] : theme.colors.gray[3], + control: { + backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark[5] : theme.colors.gray[1], + borderRadius: theme.spacing.md, + + '&:hover': { + backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark[5] : theme.colors.gray[1], + }, + }, + + content: { + margin: theme.spacing.md, + }, + + label: { + overflow: 'visible', }, })); @@ -137,6 +152,7 @@ const AppShelf = (props: any) => { const noCategory = config.services.filter( (e) => e.category === undefined || e.category === null ); + const downloadEnabled = config.modules?.[DownloadsModule.title]?.enabled ?? false; // Create an item with 0: true, 1: true, 2: true... For each category return ( // Return one item for each category @@ -147,11 +163,6 @@ const AppShelf = (props: any) => { order={2} iconPosition="right" multiple - styles={{ - item: { - borderRadius: '20px', - }, - }} initialState={toggledCategories} onChange={(idx) => settoggledCategories(idx)} > @@ -166,21 +177,23 @@ const AppShelf = (props: any) => { {item()} ) : null} - - + - - - - + }} + > + + + + + ) : null} ); diff --git a/src/components/AppShelf/AppShelfMenu.tsx b/src/components/AppShelf/AppShelfMenu.tsx index 5aada187a..78f9b10ad 100644 --- a/src/components/AppShelf/AppShelfMenu.tsx +++ b/src/components/AppShelf/AppShelfMenu.tsx @@ -20,11 +20,7 @@ export default function AppShelfMenu(props: any) { onClose={() => setOpened(false)} title="Modify a service" > - + { 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/Settings/AdvancedSettings.tsx b/src/components/Settings/AdvancedSettings.tsx index ad4517457..4c7d6a50e 100644 --- a/src/components/Settings/AdvancedSettings.tsx +++ b/src/components/Settings/AdvancedSettings.tsx @@ -37,7 +37,7 @@ export default function TitleChanger() { }; return ( - +
saveChanges(values))}> diff --git a/src/components/Settings/CommonSettings.tsx b/src/components/Settings/CommonSettings.tsx index 7b7665490..4d55eee18 100644 --- a/src/components/Settings/CommonSettings.tsx +++ b/src/components/Settings/CommonSettings.tsx @@ -1,13 +1,12 @@ -import { ActionIcon, Group, Text, SegmentedControl, TextInput, Anchor } from '@mantine/core'; +import { Group, Text, SegmentedControl, TextInput } from '@mantine/core'; import { useState } from 'react'; -import { IconBrandGithub as BrandGithub } from '@tabler/icons'; -import { CURRENT_VERSION } from '../../../data/constants'; import { useConfig } from '../../tools/state'; import { ColorSchemeSwitch } from '../ColorSchemeToggle/ColorSchemeSwitch'; import { WidgetsPositionSwitch } from '../WidgetsPositionSwitch/WidgetsPositionSwitch'; import ConfigChanger from '../Config/ConfigChanger'; import SaveConfigComponent from '../Config/SaveConfig'; import ModuleEnabler from './ModuleEnabler'; +import Tip from '../layout/Tip'; export default function CommonSettings(args: any) { const { config, setConfig } = useConfig(); @@ -25,11 +24,16 @@ export default function CommonSettings(args: any) { ); return ( - + Search engine + + Use the prefixes !yt and !t in front of your query to search on YouTube or + for a Torrent respectively. + {searchUrl === 'Custom' && ( - { - setCustomSearchUrl(event.currentTarget.value); - setConfig({ - ...config, - settings: { - ...config.settings, - searchUrl: event.currentTarget.value, - }, - }); - }} - /> + <> + %s can be used as a placeholder for the query. + { + setCustomSearchUrl(event.currentTarget.value); + setConfig({ + ...config, + settings: { + ...config.settings, + searchUrl: event.currentTarget.value, + }, + }); + }} + /> + )} @@ -73,47 +80,7 @@ export default function CommonSettings(args: any) { - - Tip: You can upload your config file by dragging and dropping it onto the page! - - - - component="a" href="https://github.com/ajnart/homarr" size="lg"> - - - - {CURRENT_VERSION} - - - - Made with ❤️ by @ - - ajnart - - - + Upload your config file by dragging and dropping it onto the page! ); } diff --git a/src/components/Settings/Credits.tsx b/src/components/Settings/Credits.tsx new file mode 100644 index 000000000..1d6271479 --- /dev/null +++ b/src/components/Settings/Credits.tsx @@ -0,0 +1,44 @@ +import { Group, ActionIcon, Anchor, Text } from '@mantine/core'; +import { IconBrandDiscord, IconBrandGithub } from '@tabler/icons'; +import { CURRENT_VERSION } from '../../../data/constants'; + +export default function Credits(props: any) { + return ( + + + component="a" href="https://github.com/ajnart/homarr" size="lg"> + + + + {CURRENT_VERSION} + + + + + Made with ❤️ by @ + + ajnart + + + component="a" href="https://discord.gg/aCsmEV5RgA" size="lg"> + + + + + ); +} diff --git a/src/components/Settings/SettingsMenu.story.tsx b/src/components/Settings/SettingsMenu.story.tsx deleted file mode 100644 index de3d13c54..000000000 --- a/src/components/Settings/SettingsMenu.story.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { SettingsMenuButton } from './SettingsMenu'; - -export default { - title: ' menu', - args: { - opened: false, - }, -}; - -export const Default = (args: any) => ; diff --git a/src/components/Settings/SettingsMenu.tsx b/src/components/Settings/SettingsMenu.tsx index e6bcb2bed..fcd6d1b91 100644 --- a/src/components/Settings/SettingsMenu.tsx +++ b/src/components/Settings/SettingsMenu.tsx @@ -1,18 +1,23 @@ -import { ActionIcon, Title, Tooltip, Drawer, Tabs } from '@mantine/core'; +import { ActionIcon, Title, Tooltip, Drawer, Tabs, ScrollArea } from '@mantine/core'; import { useHotkeys } from '@mantine/hooks'; import { useState } from 'react'; import { IconSettings } from '@tabler/icons'; import AdvancedSettings from './AdvancedSettings'; import CommonSettings from './CommonSettings'; +import Credits from './Credits'; function SettingsMenu(props: any) { return ( - + + + - + + + ); @@ -26,13 +31,14 @@ export function SettingsMenuButton(props: any) { <> Settings} + title={Settings} opened={props.opened || opened} onClose={() => setOpened(false)} > + + - - { - 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 ? <> :