diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 4bd399d2a..f44d4ef9b 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -4,8 +4,12 @@ name: Master docker CI on: push: branches: [master] + paths-ignore: + - '.github/**' + - '**.md' tags: - v* + workflow_dispatch: env: @@ -110,6 +114,6 @@ jobs: with: platforms: linux/amd64,linux/arm64,linux/arm/v7 context: . - push: ${{ github.event_name != 'pull_request' }} + push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/docker_dev.yml b/.github/workflows/docker_dev.yml index cbf6b819f..529b1cab6 100644 --- a/.github/workflows/docker_dev.yml +++ b/.github/workflows/docker_dev.yml @@ -6,7 +6,13 @@ name: Development CI on: push: branches: [dev] + paths-ignore: + - '.github/**' + - '**.md' pull_request: + paths-ignore: + - '.github/**' + - '**.md' workflow_dispatch: inputs: tags: @@ -25,13 +31,17 @@ jobs: yarn_install_and_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 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'`) @@ -39,6 +49,7 @@ jobs: 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: @@ -50,8 +61,10 @@ jobs: 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 --frozen-lockfile - run: yarn build + - name: Cache build output uses: actions/cache@v2 id: restore-build @@ -72,8 +85,10 @@ jobs: packages: write contents: read steps: + - name: Checkout uses: actions/checkout@v2 + - uses: actions/cache@v2 id: restore-build with: @@ -85,6 +100,7 @@ jobs: ./.next/standalone/ ./packages.json key: ${{ github.sha }} + - name: Docker meta id: meta uses: docker/metadata-action@v4 @@ -95,11 +111,15 @@ jobs: tags: | type=ref,event=pr tpye=raw,value=dev,priority=1 + - 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 + if: github.event_name != 'pull_request' uses: docker/login-action@v2 with: registry: ghcr.io @@ -111,6 +131,6 @@ jobs: with: platforms: linux/amd64,linux/arm64,linux/arm/v7 context: . - push: true + push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} diff --git a/.gitignore b/.gitignore index 684a71ab7..97405ae66 100644 --- a/.gitignore +++ b/.gitignore @@ -35,4 +35,5 @@ yarn-error.log* *.tsbuildinfo # storybook -storybook-static \ No newline at end of file +storybook-static +data/configs \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 95fbce619..98fe3bdbe 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,19 +1,13 @@ FROM node:16-alpine WORKDIR /app ENV NODE_ENV production -RUN addgroup --system --gid 1001 nodejs -RUN adduser --system --uid 1001 nextjs - 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 +# 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 VOLUME /app/data/configs -CMD ["node", "server.js"] \ No newline at end of file +CMD ["node", "server.js"] diff --git a/README.md b/README.md index 5433cbded..f92a99571 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,7 @@ Docker Pulls

- -

A homepage for your server. diff --git a/data/configs/config.json b/data/configs/config.json index 060961492..b9c4ae388 100644 --- a/data/configs/config.json +++ b/data/configs/config.json @@ -1,12 +1,26 @@ { "name": "config", - "services": [], + "services": [ + { + "type": "Other", + "name": "YouTube", + "icon": "https://cdn.jsdelivr.net/gh/walkxhub/dashboard-icons/png/youtube.png", + "url": "https://youtube.com/" + }, + { + "type": "Other", + "name": "YouTube ", + "icon": "https://cdn.jsdelivr.net/gh/walkxhub/dashboard-icons/png/youtube.png", + "url": "https://youtube.com/" + } + ], "settings": { "searchBar": true, - "searchUrl": "https://duckduckgo.com/?q=", + "searchUrl": "Custom", "enabledModules": [ "Date", - "Calendar" + "Calendar", + "Weather" ] } } \ No newline at end of file diff --git a/data/constants.ts b/data/constants.ts index a03b01ee9..11dbe4c3b 100644 --- a/data/constants.ts +++ b/data/constants.ts @@ -1,2 +1,2 @@ export const REPO_URL = 'ajnart/homarr'; -export const CURRENT_VERSION = 'v0.3.0'; +export const CURRENT_VERSION = 'v0.3.1'; diff --git a/src/components/AppShelf/AddAppShelfItem.tsx b/src/components/AppShelf/AddAppShelfItem.tsx index 9a413ba5d..cd6b7ff68 100644 --- a/src/components/AppShelf/AddAppShelfItem.tsx +++ b/src/components/AppShelf/AddAppShelfItem.tsx @@ -12,6 +12,7 @@ import { LoadingOverlay, ActionIcon, Tooltip, + Title, } from '@mantine/core'; import { useForm } from '@mantine/form'; import { motion } from 'framer-motion'; @@ -28,9 +29,9 @@ export function AddItemShelfButton(props: any) { Add service} opened={props.opened || opened} onClose={() => setOpened(false)} - title="Add a service" > diff --git a/src/components/AppShelf/AppShelf.tsx b/src/components/AppShelf/AppShelf.tsx index d76413811..76ec5df39 100644 --- a/src/components/AppShelf/AppShelf.tsx +++ b/src/components/AppShelf/AppShelf.tsx @@ -1,30 +1,21 @@ import React, { useState } from 'react'; import { motion } from 'framer-motion'; -import { Text, AspectRatio, SimpleGrid, Card, Image, useMantineTheme } from '@mantine/core'; +import { Text, AspectRatio, Card, Image, useMantineTheme, Center, Grid } from '@mantine/core'; import { useConfig } from '../../tools/state'; import { serviceItem } from '../../tools/types'; import AppShelfMenu from './AppShelfMenu'; -const AppShelf = () => { +const AppShelf = (props: any) => { const { config } = useConfig(); return ( - + {config.services.map((service) => ( - + + + ))} - + ); }; @@ -42,16 +33,9 @@ export function AppShelfItem(props: any) { setHovering(false); }} > - + - + {service.name} - - - + + - { - window.open(service.url); - }} - src={service.icon} - /> - - - + > + { + window.open(service.url); + }} + /> + + + + ); diff --git a/src/components/ColorSchemeToggle/ColorSchemeSwitch.tsx b/src/components/ColorSchemeToggle/ColorSchemeSwitch.tsx index b8fb15488..fda70c4d8 100644 --- a/src/components/ColorSchemeToggle/ColorSchemeSwitch.tsx +++ b/src/components/ColorSchemeToggle/ColorSchemeSwitch.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { createStyles, Switch, Group, useMantineColorScheme } from '@mantine/core'; +import { createStyles, Switch, Group, useMantineColorScheme, Kbd } from '@mantine/core'; import { Sun, MoonStars } from 'tabler-icons-react'; const useStyles = createStyles((theme) => ({ @@ -40,6 +40,9 @@ export function ColorSchemeSwitch() { toggleColorScheme()} size="md" /> Switch to {colorScheme === 'dark' ? 'light' : 'dark'} mode + + Ctrl+J + ); } diff --git a/src/components/SearchBar/SearchBar.tsx b/src/components/SearchBar/SearchBar.tsx index 7a40ea4c0..e10c8b79b 100644 --- a/src/components/SearchBar/SearchBar.tsx +++ b/src/components/SearchBar/SearchBar.tsx @@ -1,18 +1,40 @@ -import { TextInput, Text, Popover, Box } from '@mantine/core'; -import { useForm } from '@mantine/hooks'; -import { useState } from 'react'; +import { TextInput, Kbd, createStyles, useMantineTheme, Text, Popover } from '@mantine/core'; +import { useForm, useHotkeys } from '@mantine/hooks'; +import { useRef, useState } from 'react'; import { Search, BrandYoutube, Download } from 'tabler-icons-react'; import { useConfig } from '../../tools/state'; +const useStyles = createStyles((theme) => ({ + hide: { + [theme.fn.smallerThan('sm')]: { + display: 'none', + }, + display: 'flex', + alignItems: 'center', + }, +})); + export default function SearchBar(props: any) { const { config, setConfig } = useConfig(); const [opened, setOpened] = useState(false); const [icon, setIcon] = useState(); - const querryUrl = config.settings.searchUrl || 'https://www.google.com/search?q='; + const queryUrl = config.settings.searchUrl || 'https://www.google.com/search?q='; + const textInput: any = useRef(null); + useHotkeys([['ctrl+K', () => textInput.current.focus()]]); + + const { classes, cx } = useStyles(); + const theme = useMantineTheme(); + const rightSection = ( +

+ Ctrl + + + K +
+ ); const form = useForm({ initialValues: { - querry: '', + query: '', }, }); @@ -21,70 +43,66 @@ export default function SearchBar(props: any) { } return ( - { + // If query contains !yt or !t add "Searching on YouTube" or "Searching torrent" + const query = form.values.query.trim(); + const isYoutube = query.startsWith('!yt'); + const isTorrent = query.startsWith('!t'); + if (isYoutube) { + setIcon(); + } else if (isTorrent) { + setIcon(); + } else { + setIcon(); + } }} + onSubmit={form.onSubmit((values) => { + // Find if query is prefixed by !yt or !t + const query = values.query.trim(); + const isYoutube = query.startsWith('!yt'); + const isTorrent = query.startsWith('!t'); + if (isYoutube) { + window.open(`https://www.youtube.com/results?search_query=${query.substring(3)}`); + } else if (isTorrent) { + window.open(`https://bitsearch.to/search?q=${query.substring(3)}`); + } else { + window.open(`${queryUrl}${values.query}`); + } + })} > -
{ - // If querry contains !yt or !t add "Searching on YouTube" or "Searching torrent" - const querry = form.values.querry.trim(); - const isYoutube = querry.startsWith('!yt'); - const isTorrent = querry.startsWith('!t'); - if (isYoutube) { - setIcon(); - } else if (isTorrent) { - setIcon(); - } else { - setIcon(); - } - }} - onSubmit={form.onSubmit((values) => { - // Find if querry is prefixed by !yt or !t - const querry = values.querry.trim(); - const isYoutube = querry.startsWith('!yt'); - const isTorrent = querry.startsWith('!t'); - if (isYoutube) { - window.open(`https://www.youtube.com/results?search_query=${querry.substring(3)}`); - } else if (isTorrent) { - window.open(`https://bitsearch.to/search?q=${querry.substring(3)}`); - } else { - window.open(`${querryUrl}${values.querry}`); - } - })} + setOpened(true)} + onBlurCapture={() => setOpened(false)} + target={ + + } > - setOpened(true)} - onBlurCapture={() => setOpened(false)} - target={ - - } - > - - tip: Use the prefixes !yt and !t in front of your query to search on YouTube or for a Torrent respectively. - - - -
+ + tip: Use the prefixes !yt and !t in front of your query to search on YouTube + or for a Torrent respectively. + + + ); } diff --git a/src/components/Settings/SettingsMenu.tsx b/src/components/Settings/SettingsMenu.tsx index 74b5c1b93..2f821a4ef 100644 --- a/src/components/Settings/SettingsMenu.tsx +++ b/src/components/Settings/SettingsMenu.tsx @@ -74,8 +74,8 @@ function SettingsMenu(props: any) { /> {searchUrl === 'Custom' && ( { setCustomSearchUrl(event.currentTarget.value); @@ -142,7 +142,8 @@ export function SettingsMenuButton(props: any) { return ( <> Settings} opened={props.opened || opened} onClose={() => setOpened(false)} diff --git a/src/components/layout/Aside.tsx b/src/components/layout/Aside.tsx index d51055c0b..912b231ac 100644 --- a/src/components/layout/Aside.tsx +++ b/src/components/layout/Aside.tsx @@ -1,8 +1,9 @@ import { Aside as MantineAside, Group } from '@mantine/core'; +import { DateModule } from '../modules'; import { CalendarModule } from '../modules/calendar/CalendarModule'; import ModuleWrapper from '../modules/moduleWrapper'; -export default function Aside() { +export default function Aside(props: any) { return ( - + + ); diff --git a/src/components/layout/Footer.tsx b/src/components/layout/Footer.tsx index b89fb2fcb..697f02123 100644 --- a/src/components/layout/Footer.tsx +++ b/src/components/layout/Footer.tsx @@ -54,24 +54,12 @@ export function Footer({ links }: FooterCenteredProps) { )); - return ( - - - {items} - - component="a" href="https://github.com/ajnart/homarr" size="lg"> - - - + return ( + + + component="a" href="https://github.com/ajnart/homarr" size="lg"> + + ({ - root: { - position: 'relative', - zIndex: 1, - }, - - dropdown: { - position: 'absolute', - top: HEADER_HEIGHT, - left: 0, - right: 0, - zIndex: 0, - borderTopRightRadius: 0, - borderTopLeftRadius: 0, - borderTopWidth: 0, - overflow: 'hidden', - - [theme.fn.largerThan('md')]: { + hide: { + [theme.fn.smallerThan('xs')]: { display: 'none', }, }, - - header: { - display: 'flex', - height: '100%', - }, - - links: { - [theme.fn.smallerThan('md')]: { - display: 'none', - }, - }, - - burger: { - [theme.fn.largerThan('md')]: { - display: 'none', - }, - }, - - link: { - display: 'block', - lineHeight: 1, - padding: '8px 12px', - borderRadius: theme.radius.sm, - textDecoration: 'none', - color: theme.colorScheme === 'dark' ? theme.colors.dark[0] : theme.colors.gray[7], - fontSize: theme.fontSizes.sm, - fontWeight: 500, - - '&:hover': { - backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark[6] : theme.colors.gray[0], - }, - - [theme.fn.smallerThan('sm')]: { - borderRadius: 0, - padding: theme.spacing.md, - }, - }, - - linkActive: { - '&, &:hover': { - backgroundColor: - theme.colorScheme === 'dark' - ? theme.fn.rgba(theme.colors[theme.primaryColor][9], 0.25) - : theme.colors[theme.primaryColor][0], - color: theme.colors[theme.primaryColor][theme.colorScheme === 'dark' ? 3 : 7], - }, - }, })); -interface HeaderResponsiveProps { - links: { link: string; label: string }[]; -} - -export function Header({ links }: HeaderResponsiveProps) { - const [opened, toggleOpened] = useBooleanToggle(false); +export function Header(props: any) { const { classes, cx } = useStyles(); return ( - - - + + + - - + + + - - toggleOpened()} - position="right" - > - {opened ?? ( -
- -
- )} -
); } diff --git a/src/components/layout/Layout.tsx b/src/components/layout/Layout.tsx index ab646803d..8b82b4a74 100644 --- a/src/components/layout/Layout.tsx +++ b/src/components/layout/Layout.tsx @@ -1,36 +1,28 @@ -import { AppShell, Center, createStyles } from '@mantine/core'; +import { AppShell, createStyles } from '@mantine/core'; import { Header } from './Header'; import { Footer } from './Footer'; import Aside from './Aside'; -import Navbar from './Navbar'; const useStyles = createStyles((theme) => ({ - main: { - [theme.fn.largerThan('md')]: { - maxWidth: 1500, - }, - }, + main: {}, })); export default function Layout({ children, style }: any) { const { classes, cx } = useStyles(); return ( } aside={