Merge branch 'dev' into edit-mode-password

This commit is contained in:
Thomas Camlong
2023-03-22 22:22:16 +08:00
committed by GitHub
41 changed files with 501 additions and 228 deletions

View File

@@ -32,8 +32,15 @@ body:
- type: textarea - type: textarea
id: logs id: logs
attributes: attributes:
label: Additional info label: Logs
description: Logs? Screenshots? More info? description: Provide your Homarr logs so we can investigate what's going on
validations:
required: true
- type: textarea
id: context
attributes:
label: Context
description: Screenshots? More info?
validations: validations:
required: false required: false
- type: checkboxes - type: checkboxes
@@ -42,9 +49,11 @@ body:
label: Please tick the boxes label: Please tick the boxes
description: Before submitting, please ensure that description: Before submitting, please ensure that
options: options:
- label: You've read the [docs](https://github.com/ajnart/homarr#readme) - label: I confirm that I attached the proper logs
required: true required: true
- label: You've checked for [duplicate issues](https://github.com/ajnart/homarr/issues) - label: I've read the [docs](https://github.com/ajnart/homarr#readme)
required: true required: true
- label: You've tried to debug yourself - label: I've checked for [duplicate issues](https://github.com/ajnart/homarr/issues)
required: true
- label: I've tried to debug myself
required: true required: true

View File

@@ -24,11 +24,14 @@ env:
REGISTRY: ghcr.io REGISTRY: ghcr.io
# github.repository as <account>/<repo> # github.repository as <account>/<repo>
IMAGE_NAME: ${{ github.repository }} IMAGE_NAME: ${{ github.repository }}
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
jobs: jobs:
# Push image to GitHub Packages. # Push image to GitHub Packages.
# See also https://docs.docker.com/docker-hub/builds/ # See also https://docs.docker.com/docker-hub/builds/
yarn_install_and_build: yarn_install_and_build_dev:
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
packages: write packages: write
@@ -67,7 +70,7 @@ jobs:
- run: yarn install --immutable - run: yarn install --immutable
- run: yarn build - run: yarn turbo build
- name: Docker meta - name: Docker meta
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'

1
.gitignore vendored
View File

@@ -32,6 +32,7 @@ yarn-error.log*
# vercel # vercel
.vercel .vercel
.turbo
*.tsbuildinfo *.tsbuildinfo
# storybook # storybook

View File

@@ -10,6 +10,7 @@
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev",
"build": "next build", "build": "next build",
"turbo" : "turbo run build",
"analyze": "ANALYZE=true next build", "analyze": "ANALYZE=true next build",
"start": "next start", "start": "next start",
"typecheck": "tsc --noEmit", "typecheck": "tsc --noEmit",
@@ -53,7 +54,7 @@
"html-entities": "^2.3.3", "html-entities": "^2.3.3",
"i18next": "^21.9.1", "i18next": "^21.9.1",
"js-file-download": "^0.4.12", "js-file-download": "^0.4.12",
"next": "^13.1.6", "next": "^13.2.1",
"next-i18next": "^11.3.0", "next-i18next": "^11.3.0",
"nzbget-api": "^0.0.3", "nzbget-api": "^0.0.3",
"prismjs": "^1.29.0", "prismjs": "^1.29.0",
@@ -92,7 +93,7 @@
"jest": "^28.1.3", "jest": "^28.1.3",
"prettier": "^2.7.1", "prettier": "^2.7.1",
"sass": "^1.56.1", "sass": "^1.56.1",
"turbo": "^1.7.4", "turbo": "^1.8.3",
"typescript": "^4.7.4", "typescript": "^4.7.4",
"video.js": "^8.0.3" "video.js": "^8.0.3"
}, },

View File

@@ -2,6 +2,10 @@
"description": "Homarr is a <strong>sleek</strong>, <strong>modern</strong> dashboard that puts all of your apps and services at your fingertips. With Homarr, you can access and control everything in one convenient location. Homarr seamlessly integrates with the apps you've added, providing you with valuable information and giving you complete control. Installation is a breeze, and Homarr supports a wide range of deployment methods.", "description": "Homarr is a <strong>sleek</strong>, <strong>modern</strong> dashboard that puts all of your apps and services at your fingertips. With Homarr, you can access and control everything in one convenient location. Homarr seamlessly integrates with the apps you've added, providing you with valuable information and giving you complete control. Installation is a breeze, and Homarr supports a wide range of deployment methods.",
"contact": "Having trouble or questions? Connect with us!", "contact": "Having trouble or questions? Connect with us!",
"addToDashboard": "Add to Dashboard", "addToDashboard": "Add to Dashboard",
"tip": "Mod refers to your modifier key, it is Ctrl and Command/Super/Windows key",
"key": "Shortcut key",
"action": "Action",
"keybinds": "Keybinds",
"metrics": { "metrics": {
"configurationSchemaVersion": "Configuration schema version", "configurationSchemaVersion": "Configuration schema version",
"configurationsCount": "Available configurations", "configurationsCount": "Available configurations",

View File

@@ -0,0 +1,14 @@
{
"card": {
"title": "Oops, there was an error!",
"buttons": {
"details": "Details",
"tryAgain": "Try again"
}
},
"modal": {
"text": "We're sorry for the inconvinience! This shouln't happen - please report this issue on GitHub.",
"label": "Your error",
"reportButton": "Report this error"
}
}

View File

@@ -1,13 +1,14 @@
import { import {
Accordion,
ActionIcon, ActionIcon,
Anchor, Anchor,
Badge, Badge,
Button, Button,
createStyles, createStyles,
Divider,
Grid, Grid,
Group, Group,
HoverCard, HoverCard,
Kbd,
Modal, Modal,
Table, Table,
Text, Text,
@@ -36,6 +37,7 @@ import { useConfigStore } from '../../../../config/store';
import { useEditModeInformationStore } from '../../../../hooks/useEditModeInformation'; import { useEditModeInformationStore } from '../../../../hooks/useEditModeInformation';
import { usePackageAttributesStore } from '../../../../tools/client/zustands/usePackageAttributesStore'; import { usePackageAttributesStore } from '../../../../tools/client/zustands/usePackageAttributesStore';
import { useColorTheme } from '../../../../tools/color'; import { useColorTheme } from '../../../../tools/color';
import Tip from '../../../layout/Tip';
import { usePrimaryGradient } from '../../../layout/useGradient'; import { usePrimaryGradient } from '../../../layout/useGradient';
import Credits from '../../../Settings/Common/Credits'; import Credits from '../../../Settings/Common/Credits';
@@ -51,6 +53,23 @@ export const AboutModal = ({ opened, closeModal, newVersionAvailable }: AboutMod
const informations = useInformationTableItems(newVersionAvailable); const informations = useInformationTableItems(newVersionAvailable);
const { t } = useTranslation(['common', 'layout/modals/about']); const { t } = useTranslation(['common', 'layout/modals/about']);
const keybinds = [
{ key: 'Mod + J', shortcut: 'Toggle light/dark mode' },
{ key: 'Mod + K', shortcut: 'Focus on search bar' },
{ key: 'Mod + B', shortcut: 'Open docker widget' },
{ key: 'Mod + E', shortcut: 'Toggle Edit mode' },
];
const rows = keybinds.map((element) => (
<tr key={element.key}>
<td>
<Kbd>{element.key}</Kbd>
</td>
<td>
<Text>{element.shortcut}</Text>
</td>
</tr>
));
return ( return (
<Modal <Modal
onClose={() => closeModal()} onClose={() => closeModal()}
@@ -77,7 +96,7 @@ export const AboutModal = ({ opened, closeModal, newVersionAvailable }: AboutMod
<Trans i18nKey="layout/modals/about:description" /> <Trans i18nKey="layout/modals/about:description" />
</Text> </Text>
<Table mb="lg" striped highlightOnHover withBorder> <Table mb="lg" highlightOnHover withBorder>
<tbody> <tbody>
{informations.map((item, index) => ( {informations.map((item, index) => (
<tr key={index}> <tr key={index}>
@@ -101,8 +120,26 @@ export const AboutModal = ({ opened, closeModal, newVersionAvailable }: AboutMod
))} ))}
</tbody> </tbody>
</Table> </Table>
<Accordion mb={5} variant="contained" radius="md">
<Accordion.Item value="keybinds">
<Accordion.Control icon={<IconKey size={20} />}>
{t('layout/modals/about:keybinds')}
</Accordion.Control>
<Accordion.Panel>
<Table mb={5}>
<thead>
<tr>
<th>{t('layout/modals/about:key')}</th>
<th>{t('layout/modals/about:action')}</th>
</tr>
</thead>
<tbody>{rows}</tbody>
</Table>
<Tip>{t('layout/modals/about:tip')}</Tip>
</Accordion.Panel>
</Accordion.Item>
</Accordion>
<Divider variant="dashed" mb="md" />
<Title order={6} mb="xs" align="center"> <Title order={6} mb="xs" align="center">
{t('layout/modals/about:contact')} {t('layout/modals/about:contact')}
</Title> </Title>

View File

@@ -90,7 +90,8 @@ export const IconSelector = ({
} }
variant="default" variant="default"
withAsterisk withAsterisk
dropdownComponent={(props: any) => <ScrollArea {...props} mah={400} />} dropdownComponent={(props: any) => <ScrollArea {...props} mah={250} />}
dropdownPosition="bottom"
required required
onChange={(event) => { onChange={(event) => {
if (allowAppNamePropagation) { if (allowAppNamePropagation) {

View File

@@ -6,7 +6,6 @@ import {
Grid, Grid,
Group, Group,
PasswordInput, PasswordInput,
Stack,
ThemeIcon, ThemeIcon,
Title, Title,
Text, Text,
@@ -40,7 +39,7 @@ export const GenericSecretInput = ({
const Icon = setIcon; const Icon = setIcon;
const [displayUpdateField, setDisplayUpdateField] = useState<boolean>(false); const [displayUpdateField, setDisplayUpdateField] = useState<boolean>(!secretIsPresent);
const { t } = useTranslation(['layout/modals/add-app', 'common']); const { t } = useTranslation(['layout/modals/add-app', 'common']);
return ( return (
@@ -51,26 +50,26 @@ export const GenericSecretInput = ({
<ThemeIcon color={secretIsPresent ? 'green' : 'red'} variant="light" size="lg"> <ThemeIcon color={secretIsPresent ? 'green' : 'red'} variant="light" size="lg">
<Icon size={18} /> <Icon size={18} />
</ThemeIcon> </ThemeIcon>
<Stack spacing={0}> <Flex justify="start" align="start" direction="column">
<Group spacing="xs"> <Group spacing="xs">
<Title className={classes.subtitle} order={6}> <Title className={classes.subtitle} order={6}>
{t(label)} {t(label)}
</Title> </Title>
<Group spacing="xs"> <Group spacing="xs">
{secretIsPresent ? ( <Badge
<Badge className={classes.textTransformUnset} color="green" variant="dot"> className={classes.textTransformUnset}
{t('integration.type.defined')} color={secretIsPresent ? 'green' : 'red'}
variant="dot"
>
{secretIsPresent
? t('integration.type.defined')
: t('integration.type.undefined')}
</Badge> </Badge>
) : (
<Badge className={classes.textTransformUnset} color="red" variant="dot">
{t('integration.type.undefined')}
</Badge>
)}
{type === 'private' ? ( {type === 'private' ? (
<Tooltip <Tooltip
label={t('integration.type.explanationPrivate')} label={t('integration.type.explanationPrivate')}
width={200} width={400}
multiline multiline
withinPortal withinPortal
withArrow withArrow
@@ -82,7 +81,7 @@ export const GenericSecretInput = ({
) : ( ) : (
<Tooltip <Tooltip
label={t('integration.type.explanationPublic')} label={t('integration.type.explanationPublic')}
width={200} width={400}
multiline multiline
withinPortal withinPortal
withArrow withArrow
@@ -94,29 +93,20 @@ export const GenericSecretInput = ({
)} )}
</Group> </Group>
</Group> </Group>
<Text size="xs" color="dimmed"> <Text size="xs" color="dimmed" w={400}>
{type === 'private' {type === 'private'
? 'Private: Once saved, you cannot read out this value again' ? 'Private: Once saved, you cannot read out this value again'
: 'Public: Can be read out repeatedly'} : 'Public: Can be read out repeatedly'}
</Text> </Text>
</Stack> </Flex>
</Group> </Group>
</Grid.Col> </Grid.Col>
<Grid.Col xs={12} md={6}> <Grid.Col xs={12} md={6}>
<Flex gap={10} justify="end" align="end"> <Flex gap={10} justify="end" align="end">
<Button
onClick={() => {
setDisplayUpdateField(false);
onClickUpdateButton(undefined);
}}
variant="subtle"
color="gray"
px="xl"
>
{t('integration.secrets.clear')}
</Button>
{displayUpdateField === true ? ( {displayUpdateField === true ? (
<PasswordInput <PasswordInput
required
defaultValue={value}
placeholder="new secret" placeholder="new secret"
styles={{ root: { width: 200 } }} styles={{ root: { width: 200 } }}
{...props} {...props}

View File

@@ -10,6 +10,9 @@ interface NetworkTabProps {
export const NetworkTab = ({ form }: NetworkTabProps) => { export const NetworkTab = ({ form }: NetworkTabProps) => {
const { t } = useTranslation('layout/modals/add-app'); const { t } = useTranslation('layout/modals/add-app');
const acceptableStatusCodes = (form.values.network.statusCodes ?? ['200']).map((x) =>
x.toString()
);
return ( return (
<Tabs.Panel value="network" pt="lg"> <Tabs.Panel value="network" pt="lg">
<Switch <Switch
@@ -27,7 +30,7 @@ export const NetworkTab = ({ form }: NetworkTabProps) => {
data={StatusCodes} data={StatusCodes}
clearable clearable
searchable searchable
defaultValue={form.values.network.okStatus.map((x) => `${x}`)} defaultValue={acceptableStatusCodes}
variant="default" variant="default"
{...form.getInputProps('network.statusCodes')} {...form.getInputProps('network.statusCodes')}
/> />

View File

@@ -95,7 +95,7 @@ export const AvailableElementTypes = ({
}, },
network: { network: {
enabledStatusChecker: true, enabledStatusChecker: true,
okStatus: [200], statusCodes: ['200'],
}, },
behaviour: { behaviour: {
isOpeningNewTab: true, isOpeningNewTab: true,

View File

@@ -19,7 +19,7 @@ export const AppPing = ({ app }: AppPingProps) => {
queryKey: ['ping', { id: app.id, name: app.name }], queryKey: ['ping', { id: app.id, name: app.name }],
queryFn: async () => { queryFn: async () => {
const response = await fetch(`/api/modules/ping?url=${encodeURI(app.url)}`); const response = await fetch(`/api/modules/ping?url=${encodeURI(app.url)}`);
const isOk = app.network.okStatus.includes(response.status); const isOk = app.network.statusCodes.includes(response.status.toString());
return { return {
status: response.status, status: response.status,
state: isOk ? 'online' : 'down', state: isOk ? 'online' : 'down',
@@ -60,5 +60,3 @@ export const AppPing = ({ app }: AppPingProps) => {
</motion.div> </motion.div>
); );
}; };
type PingState = 'loading' | 'down' | 'online';

View File

@@ -36,7 +36,13 @@ export const AppTile = ({ className, app }: AppTileProps) => {
className="dashboard-tile-app" className="dashboard-tile-app"
> >
<Box hidden={false}> <Box hidden={false}>
<Title order={5} size="md" ta="center" lineClamp={1} className={cx(classes.appName, 'dashboard-tile-app-title')}> <Title
order={5}
size="md"
ta="center"
lineClamp={1}
className={cx(classes.appName, 'dashboard-tile-app-title')}
>
{app.name} {app.name}
</Title> </Title>
</Box> </Box>

View File

@@ -24,9 +24,9 @@ export const GenericTileMenu = ({
} }
return ( return (
<Menu withinPortal withArrow position="right-start"> <Menu withinPortal withArrow position="right">
<Menu.Target> <Menu.Target>
<ActionIcon pos="absolute" top={4} right={4}> <ActionIcon size="md" radius="md" variant="light" pos="absolute" top={8} right={8}>
<IconDots /> <IconDots />
</ActionIcon> </ActionIcon>
</Menu.Target> </Menu.Target>

View File

@@ -10,9 +10,7 @@ export default function CustomizationSettings() {
return ( return (
<ScrollArea style={{ height: height - 100 }} offsetScrollbars> <ScrollArea style={{ height: height - 100 }} offsetScrollbars>
<Stack mt="xs" mb="md" spacing="xs"> <Stack mt="xs" mb="md" spacing="xs">
<Text color="dimmed"> <Text color="dimmed">{t('text')}</Text>
{t('text')}
</Text>
<CustomizationSettingsAccordeon /> <CustomizationSettingsAccordeon />
</Stack> </Stack>
</ScrollArea> </ScrollArea>

View File

@@ -8,7 +8,9 @@ export const LogoImageChanger = () => {
const { t } = useTranslation('settings/customization/page-appearance'); const { t } = useTranslation('settings/customization/page-appearance');
const updateConfig = useConfigStore((x) => x.updateConfig); const updateConfig = useConfigStore((x) => x.updateConfig);
const { config, name: configName } = useConfigContext(); const { config, name: configName } = useConfigContext();
const [logoImageSrc, setLogoImageSrc] = useState(config?.settings.customization.logoImageUrl ?? '/imgs/logo/logo.png'); const [logoImageSrc, setLogoImageSrc] = useState(
config?.settings.customization.logoImageUrl ?? '/imgs/logo/logo.png'
);
if (!configName) return null; if (!configName) return null;

View File

@@ -1,18 +1,20 @@
import { ActionIcon, Button, Group, Text, Title, Tooltip } from '@mantine/core';
import { useHotkeys, useWindowEvent } from '@mantine/hooks';
import { hideNotification, showNotification } from '@mantine/notifications';
import { IconEditCircle, IconEditCircleOff } from '@tabler/icons';
import axios from 'axios'; import axios from 'axios';
import Consola from 'consola'; import Consola from 'consola';
import { ActionIcon, Button, Group, Text, Title, Tooltip } from '@mantine/core';
import { IconEditCircle, IconEditCircleOff } from '@tabler/icons';
import { getCookie } from 'cookies-next'; import { getCookie } from 'cookies-next';
import { Trans, useTranslation } from 'next-i18next'; import { Trans, useTranslation } from 'next-i18next';
import { useHotkeys } from '@mantine/hooks';
import { hideNotification, showNotification } from '@mantine/notifications';
import { useConfigContext } from '../../../../../config/provider'; import { useConfigContext } from '../../../../../config/provider';
import { useScreenSmallerThan } from '../../../../../hooks/useScreenSmallerThan'; import { useScreenSmallerThan } from '../../../../../hooks/useScreenSmallerThan';
import { useEditModeStore } from '../../../../Dashboard/Views/useEditModeStore'; import { useEditModeStore } from '../../../../Dashboard/Views/useEditModeStore';
import { AddElementAction } from '../AddElementAction/AddElementAction';
import { useNamedWrapperColumnCount } from '../../../../Dashboard/Wrappers/gridstack/store'; import { useNamedWrapperColumnCount } from '../../../../Dashboard/Wrappers/gridstack/store';
import { useCardStyles } from '../../../useCardStyles'; import { useCardStyles } from '../../../useCardStyles';
import { AddElementAction } from '../AddElementAction/AddElementAction';
const beforeUnloadEventText = 'Exit the edit mode to save your changes';
export const ToggleEditModeAction = () => { export const ToggleEditModeAction = () => {
const { enabled, toggleEditMode } = useEditModeStore(); const { enabled, toggleEditMode } = useEditModeStore();
@@ -27,7 +29,17 @@ export const ToggleEditModeAction = () => {
const { config } = useConfigContext(); const { config } = useConfigContext();
const { classes } = useCardStyles(true); const { classes } = useCardStyles(true);
useHotkeys([['ctrl+E', toggleEditMode]]); useHotkeys([['mod+E', toggleEditMode]]);
useWindowEvent('beforeunload', (event: BeforeUnloadEvent) => {
if (enabled) {
// eslint-disable-next-line no-param-reassign
event.returnValue = beforeUnloadEventText;
return beforeUnloadEventText;
}
return undefined;
});
const toggleButtonClicked = () => { const toggleButtonClicked = () => {
toggleEditMode(); toggleEditMode();

View File

@@ -10,5 +10,7 @@ export const useGetDashboardIcons = () =>
return data as NormalizedIconRepositoryResult[]; return data as NormalizedIconRepositoryResult[];
}, },
refetchOnMount: false, refetchOnMount: false,
// Cache for infinity, refetch every so often.
cacheTime: Infinity,
refetchOnWindowFocus: false, refetchOnWindowFocus: false,
}); });

View File

@@ -1,11 +1,12 @@
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { NormalizedDownloadQueueResponse } from '../../../types/api/downloads/queue/NormalizedDownloadQueueResponse'; import { NormalizedDownloadQueueResponse } from '../../../types/api/downloads/queue/NormalizedDownloadQueueResponse';
export const useGetDownloadClientsQueue = () => useQuery({ export const useGetDownloadClientsQueue = () =>
useQuery({
queryKey: ['network-speed'], queryKey: ['network-speed'],
queryFn: async (): Promise<NormalizedDownloadQueueResponse> => { queryFn: async (): Promise<NormalizedDownloadQueueResponse> => {
const response = await fetch('/api/modules/downloads'); const response = await fetch('/api/modules/downloads');
return response.json(); return response.json();
}, },
refetchInterval: 3000, refetchInterval: 3000,
}); });

View File

@@ -1,8 +1,8 @@
import { NextFetchEvent, NextRequest, NextResponse } from 'next/server'; import { NextRequest, NextResponse } from 'next/server';
// eslint-disable-next-line consistent-return export function middleware(req: NextRequest) {
export function middleware(req: NextRequest, ev: NextFetchEvent) {
const { cookies } = req; const { cookies } = req;
// Don't even bother with the middleware if there is no defined password // Don't even bother with the middleware if there is no defined password
if (!process.env.PASSWORD) return NextResponse.next(); if (!process.env.PASSWORD) return NextResponse.next();

View File

@@ -177,7 +177,7 @@ export default function ContainerActionBar({ selected, reload }: ContainerAction
}, },
network: { network: {
enabledStatusChecker: true, enabledStatusChecker: true,
okStatus: [200], statusCodes: ['200'],
}, },
behaviour: { behaviour: {
isOpeningNewTab: true, isOpeningNewTab: true,

View File

@@ -38,6 +38,7 @@ function App(
colorScheme: ColorScheme; colorScheme: ColorScheme;
packageAttributes: ServerSidePackageAttributesType; packageAttributes: ServerSidePackageAttributesType;
editModeEnabled: boolean; editModeEnabled: boolean;
defaultColorScheme: ColorScheme;
} }
) { ) {
const { Component, pageProps } = props; const { Component, pageProps } = props;
@@ -55,7 +56,7 @@ function App(
// hook will return either 'dark' or 'light' on client // hook will return either 'dark' or 'light' on client
// and always 'light' during ssr as window.matchMedia is not available // and always 'light' during ssr as window.matchMedia is not available
const preferredColorScheme = useColorScheme(); const preferredColorScheme = useColorScheme(props.defaultColorScheme);
const [colorScheme, setColorScheme] = useLocalStorage<ColorScheme>({ const [colorScheme, setColorScheme] = useLocalStorage<ColorScheme>({
key: 'mantine-color-scheme', key: 'mantine-color-scheme',
defaultValue: preferredColorScheme, defaultValue: preferredColorScheme,
@@ -144,10 +145,18 @@ App.getInitialProps = ({ ctx }: { ctx: GetServerSidePropsContext }) => {
'EXPERIMENTAL: You have disabled the edit mode. Modifications are no longer possible and any requests on the API will be dropped. If you want to disable this, unset the DISABLE_EDIT_MODE environment variable. This behaviour may be removed in future versions of Homarr' 'EXPERIMENTAL: You have disabled the edit mode. Modifications are no longer possible and any requests on the API will be dropped. If you want to disable this, unset the DISABLE_EDIT_MODE environment variable. This behaviour may be removed in future versions of Homarr'
); );
} }
if (process.env.DEFAULT_COLOR_SCHEME !== undefined) {
Consola.debug(`Overriding the default color scheme with ${process.env.DEFAULT_COLOR_SCHEME}`);
}
const colorScheme: ColorScheme = process.env.DEFAULT_COLOR_SCHEME as ColorScheme ?? 'light';
return { return {
colorScheme: getCookie('color-scheme', ctx) || 'light', colorScheme: getCookie('color-scheme', ctx) || 'light',
packageAttributes: getServiceSidePackageAttributes(), packageAttributes: getServiceSidePackageAttributes(),
editModeEnabled: !disableEditMode, editModeEnabled: !disableEditMode,
defaultColorScheme: colorScheme,
}; };
}; };

View File

@@ -6,10 +6,26 @@ import { UnpkgIconsRepository } from '../../../tools/server/images/unpkg-icons-r
const Get = async (request: NextApiRequest, response: NextApiResponse) => { const Get = async (request: NextApiRequest, response: NextApiResponse) => {
const respositories = [ const respositories = [
new LocalIconsRepository(), new LocalIconsRepository(),
new JsdelivrIconsRepository(JsdelivrIconsRepository.tablerRepository, 'Walkxcode Dashboard Icons', 'Walkxcode on Github'), new JsdelivrIconsRepository(
new UnpkgIconsRepository(UnpkgIconsRepository.tablerRepository, 'Tabler Icons', 'Tabler Icons - GitHub (MIT)'), JsdelivrIconsRepository.tablerRepository,
new JsdelivrIconsRepository(JsdelivrIconsRepository.papirusRepository, 'Papirus Icons', 'Papirus Development Team on GitHub (Apache 2.0)'), 'Walkxcode Dashboard Icons',
new JsdelivrIconsRepository(JsdelivrIconsRepository.homelabSvgAssetsRepository, 'Homelab Svg Assets', 'loganmarchione on GitHub (MIT)'), 'Walkxcode on Github'
),
new UnpkgIconsRepository(
UnpkgIconsRepository.tablerRepository,
'Tabler Icons',
'Tabler Icons - GitHub (MIT)'
),
new JsdelivrIconsRepository(
JsdelivrIconsRepository.papirusRepository,
'Papirus Icons',
'Papirus Development Team on GitHub (Apache 2.0)'
),
new JsdelivrIconsRepository(
JsdelivrIconsRepository.homelabSvgAssetsRepository,
'Homelab Svg Assets',
'loganmarchione on GitHub (MIT)'
),
]; ];
const fetches = respositories.map((rep) => rep.fetch()); const fetches = respositories.map((rep) => rep.fetch());
const data = await Promise.all(fetches); const data = await Promise.all(fetches);

View File

@@ -61,7 +61,9 @@ const Get = async (request: NextApiRequest, response: NextApiResponse) => {
const responseBody = { apps: data, failedApps: failedClients } as NormalizedDownloadQueueResponse; const responseBody = { apps: data, failedApps: failedClients } as NormalizedDownloadQueueResponse;
if (failedClients.length > 0) { if (failedClients.length > 0) {
Consola.warn(`${failedClients.length} download clients failed. Please check your configuration and the above log`); Consola.warn(
`${failedClients.length} download clients failed. Please check your configuration and the above log`
);
} }
return response.status(200).json(responseBody); return response.status(200).json(responseBody);

View File

@@ -53,6 +53,7 @@ export const Get = async (request: NextApiRequest, response: NextApiResponse) =>
title: item.title ? decode(item.title) : undefined, title: item.title ? decode(item.title) : undefined,
content: decode(item.content), content: decode(item.content),
enclosure: createEnclosure(item), enclosure: createEnclosure(item),
link: createLink(item),
})) }))
.sort((a: { pubDate: number }, b: { pubDate: number }) => { .sort((a: { pubDate: number }, b: { pubDate: number }) => {
if (!a.pubDate || !b.pubDate) { if (!a.pubDate || !b.pubDate) {
@@ -70,6 +71,14 @@ export const Get = async (request: NextApiRequest, response: NextApiResponse) =>
}); });
}; };
const createLink = (item: any) => {
if (item.link) {
return item.link;
}
return item.guid;
};
const createEnclosure = (item: any) => { const createEnclosure = (item: any) => {
if (item.enclosure) { if (item.enclosure) {
return item.enclosure; return item.enclosure;

View File

@@ -159,7 +159,7 @@ const migrateService = (oldService: serviceItem, areaType: AreaType): ConfigAppT
}, },
network: { network: {
enabledStatusChecker: oldService.ping ?? true, enabledStatusChecker: oldService.ping ?? true,
okStatus: oldService.status?.map((str) => parseInt(str, 10)) ?? [200], statusCodes: oldService.status ?? ['200'],
}, },
appearance: { appearance: {
iconUrl: migrateIcon(oldService.icon), iconUrl: migrateIcon(oldService.icon),

View File

@@ -23,7 +23,7 @@ export class JsdelivrIconsRepository extends AbstractIconRepository {
constructor( constructor(
private readonly repository: JsdelivrRepositoryUrl, private readonly repository: JsdelivrRepositoryUrl,
private readonly displayName: string, private readonly displayName: string,
copyright: string, copyright: string
) { ) {
super(copyright); super(copyright);
} }

View File

@@ -36,6 +36,7 @@ export const dashboardNamespaces = [
'modules/media-server', 'modules/media-server',
'modules/common-media-cards', 'modules/common-media-cards',
'modules/video-stream', 'modules/video-stream',
'widgets/error-boundary',
]; ];
export const loginNamespaces = ['authentication/login']; export const loginNamespaces = ['authentication/login'];

View File

@@ -14,18 +14,23 @@ export type GenericCurrentlyPlaying = {
episodeCount: number | undefined; episodeCount: number | undefined;
type: 'audio' | 'video' | 'tv' | 'movie' | undefined; type: 'audio' | 'video' | 'tv' | 'movie' | undefined;
metadata: { metadata: {
video: { video:
| {
videoCodec: string | undefined; videoCodec: string | undefined;
videoFrameRate: string | undefined; videoFrameRate: string | undefined;
height: number | undefined; height: number | undefined;
width: number | undefined; width: number | undefined;
bitrate: number | undefined; bitrate: number | undefined;
} | undefined; }
audio: { | undefined;
audio:
| {
audioCodec: string | undefined; audioCodec: string | undefined;
audioChannels: number | undefined; audioChannels: number | undefined;
} | undefined; }
transcoding: { | undefined;
transcoding:
| {
context: string | undefined; context: string | undefined;
sourceVideoCodec: string | undefined; sourceVideoCodec: string | undefined;
sourceAudioCodec: string | undefined; sourceAudioCodec: string | undefined;
@@ -41,6 +46,7 @@ export type GenericCurrentlyPlaying = {
height: number | undefined; height: number | undefined;
transcodeHwRequested: boolean | undefined; transcodeHwRequested: boolean | undefined;
timeStamp: number | undefined; timeStamp: number | undefined;
} | undefined; }
| undefined;
}; };
}; };

View File

@@ -23,7 +23,7 @@ interface AppBehaviourType {
interface AppNetworkType { interface AppNetworkType {
enabledStatusChecker: boolean; enabledStatusChecker: boolean;
okStatus: number[]; statusCodes: string[];
} }
interface AppAppearanceType { interface AppAppearanceType {

View File

@@ -2,6 +2,7 @@ import { ComponentType, useMemo } from 'react';
import Widgets from '.'; import Widgets from '.';
import { HomarrCardWrapper } from '../components/Dashboard/Tiles/HomarrCardWrapper'; import { HomarrCardWrapper } from '../components/Dashboard/Tiles/HomarrCardWrapper';
import { WidgetsMenu } from '../components/Dashboard/Tiles/Widgets/WidgetsMenu'; import { WidgetsMenu } from '../components/Dashboard/Tiles/Widgets/WidgetsMenu';
import ErrorBoundary from './boundary';
import { IWidget } from './widgets'; import { IWidget } from './widgets';
interface WidgetWrapperProps { interface WidgetWrapperProps {
@@ -40,9 +41,11 @@ export const WidgetWrapper = ({
const widgetWithDefaultProps = useWidget(widget); const widgetWithDefaultProps = useWidget(widget);
return ( return (
<ErrorBoundary>
<HomarrCardWrapper className={className}> <HomarrCardWrapper className={className}>
<WidgetsMenu integration={widgetId} widget={widgetWithDefaultProps} /> <WidgetsMenu integration={widgetId} widget={widgetWithDefaultProps} />
<WidgetComponent widget={widgetWithDefaultProps} /> <WidgetComponent widget={widgetWithDefaultProps} />
</HomarrCardWrapper> </HomarrCardWrapper>
</ErrorBoundary>
); );
}; };

127
src/widgets/boundary.tsx Normal file
View File

@@ -0,0 +1,127 @@
import Consola from 'consola';
import React, { ReactNode } from 'react';
import { openModal } from '@mantine/modals';
import { withTranslation } from 'next-i18next';
import { Button, Card, Center, Code, Group, Stack, Text, Title } from '@mantine/core';
import { IconBrandGithub, IconBug, IconInfoCircle, IconRefresh } from '@tabler/icons';
type ErrorBoundaryState = {
hasError: boolean;
error: Error | undefined;
};
type ErrorBoundaryProps = {
t: (key: string) => string;
children: ReactNode;
};
/**
* A custom error boundary, that catches errors within widgets and renders an error component.
* The error component can be refreshed and shows a modal with error details
*/
class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
constructor(props: any) {
super(props);
// Define a state variable to track whether is an error or not
this.state = { hasError: false, error: undefined };
}
static getDerivedStateFromError(error: Error) {
// Update state so the next render will show the fallback UI
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: any) {
Consola.error(`Error while rendering widget, ${error}: ${errorInfo}`);
}
render() {
// Check if the error is thrown
if (this.state.hasError) {
return (
<Card
m={10}
sx={(theme) => ({
backgroundColor: theme.colors.red[5],
})}
radius="lg"
shadow="sm"
withBorder
>
<Center>
<Stack align="center">
<IconBug color="white" />
<Stack spacing={0} align="center">
<Title order={4} color="white" align="center">
{this.props.t('card.title')}
</Title>
{this.state.error && (
<Text color="white" align="center" size="sm">
{this.state.error.toString()}
</Text>
)}
</Stack>
<Group>
<Button
onClick={() =>
openModal({
title: 'Your widget had an error',
children: (
<>
<Text size="sm" mb="sm">
{this.props.t('modal.text')}
</Text>
{this.state.error && (
<>
<Text weight="bold" size="sm">
{this.props.t('modal.label')}
</Text>
<Code block>{this.state.error.toString()}</Code>
</>
)}
<Button
sx={(theme) => ({
backgroundColor: theme.colors.gray[8],
'&:hover': {
backgroundColor: theme.colors.gray[9],
},
})}
leftIcon={<IconBrandGithub />}
component="a"
href="https://github.com/ajnart/homarr/issues/new?assignees=&labels=%F0%9F%90%9B+Bug&template=bug.yml&title=New%20bug"
target="_blank"
mt="md"
fullWidth
>
{(this.props.t('modal.reportButton'))}
</Button>
</>
),
})
}
leftIcon={<IconInfoCircle size={16} />}
variant="light"
>
{this.props.t('card.buttons.details')}
</Button>
<Button
onClick={() => this.setState({ hasError: false })}
leftIcon={<IconRefresh size={16} />}
variant="light"
>
{this.props.t('card.buttons.tryAgain')}
</Button>
</Group>
</Stack>
</Center>
</Card>
);
}
// Return children components in case of no error
return this.props.children;
}
}
export default withTranslation('widgets/error-boundary')(ErrorBoundary);

View File

@@ -25,7 +25,7 @@ const definition = defineWidget({
component: DateTile, component: DateTile,
}); });
export type IDateWidget = IWidget<typeof definition['id'], typeof definition>; export type IDateWidget = IWidget<(typeof definition)['id'], typeof definition>;
interface DateTileProps { interface DateTileProps {
widget: IDateWidget; widget: IDateWidget;

View File

@@ -107,7 +107,9 @@ export const DetailCollapseable = ({ session }: { session: GenericSessionInfo })
</Group> </Group>
<Text>{session.sessionName}</Text> <Text>{session.sessionName}</Text>
</Flex> </Flex>
{details.length > 0 && <Divider label="Stats for nerds" labelPosition="center" mt="lg" mb="sm" />} {details.length > 0 && (
<Divider label="Stats for nerds" labelPosition="center" mt="lg" mb="sm" />
)}
<Grid> <Grid>
{details.map((detail, index) => ( {details.map((detail, index) => (
<Grid.Col xs={12} sm={6} key={index}> <Grid.Col xs={12} sm={6} key={index}>

View File

@@ -16,7 +16,6 @@ import {
Title, Title,
UnstyledButton, UnstyledButton,
} from '@mantine/core'; } from '@mantine/core';
import { useElementSize } from '@mantine/hooks';
import { import {
IconBulldozer, IconBulldozer,
IconCalendarTime, IconCalendarTime,
@@ -65,7 +64,6 @@ function RssTile({ widget }: RssTileProps) {
); );
const { classes } = useStyles(); const { classes } = useStyles();
const [loadingOverlayVisible, setLoadingOverlayVisible] = useState(false); const [loadingOverlayVisible, setLoadingOverlayVisible] = useState(false);
const { ref, height } = useElementSize();
if (!data || isLoading) { if (!data || isLoading) {
return ( return (
@@ -88,7 +86,7 @@ function RssTile({ widget }: RssTileProps) {
} }
return ( return (
<Stack ref={ref} h="100%"> <Stack h="100%">
<LoadingOverlay visible={loadingOverlayVisible} /> <LoadingOverlay visible={loadingOverlayVisible} />
<Flex gap="md"> <Flex gap="md">
{data.feed.image ? ( {data.feed.image ? (
@@ -121,7 +119,7 @@ function RssTile({ widget }: RssTileProps) {
<Card <Card
key={index} key={index}
withBorder withBorder
component={Link} component={Link ?? 'div'}
href={item.link} href={item.link}
radius="md" radius="md"
target="_blank" target="_blank"
@@ -137,6 +135,7 @@ function RssTile({ widget }: RssTileProps) {
)} )}
<Flex gap="xs"> <Flex gap="xs">
{item.enclosure && (
<MediaQuery query="(max-width: 1200px)" styles={{ display: 'none' }}> <MediaQuery query="(max-width: 1200px)" styles={{ display: 'none' }}>
<Image <Image
src={item.enclosure?.url ?? undefined} src={item.enclosure?.url ?? undefined}
@@ -146,7 +145,8 @@ function RssTile({ widget }: RssTileProps) {
withPlaceholder withPlaceholder
/> />
</MediaQuery> </MediaQuery>
<Flex gap={2} direction="column"> )}
<Flex gap={2} direction="column" w="100%">
{item.categories && ( {item.categories && (
<Flex gap="xs" wrap="wrap" h={20} style={{ overflow: 'hidden' }}> <Flex gap="xs" wrap="wrap" h={20} style={{ overflow: 'hidden' }}>
{item.categories.map((category: any, categoryIndex: number) => ( {item.categories.map((category: any, categoryIndex: number) => (
@@ -181,12 +181,14 @@ function RssTile({ widget }: RssTileProps) {
{data.feed.pubDate} {data.feed.pubDate}
</Text> </Text>
</Group> </Group>
{data.feed.lastBuildDate && (
<Group> <Group>
<IconBulldozer size={14} /> <IconBulldozer size={14} />
<Text color="dimmed" size="sm"> <Text color="dimmed" size="sm">
{data.feed.lastBuildDate} {data.feed.lastBuildDate}
</Text> </Text>
</Group> </Group>
)}
{data.feed.feedUrl && ( {data.feed.feedUrl && (
<Group spacing="sm"> <Group spacing="sm">
<IconSpeakerphone size={14} /> <IconSpeakerphone size={14} />

View File

@@ -46,7 +46,7 @@ const definition = defineWidget({
}, },
}); });
export type IUsenetWidget = IWidget<typeof definition['id'], typeof definition>; export type IUsenetWidget = IWidget<(typeof definition)['id'], typeof definition>;
interface UseNetTileProps { interface UseNetTileProps {
widget: IUsenetWidget; widget: IUsenetWidget;

View File

@@ -28,7 +28,7 @@ const definition = defineWidget({
component: WeatherTile, component: WeatherTile,
}); });
export type IWeatherWidget = IWidget<typeof definition['id'], typeof definition>; export type IWeatherWidget = IWidget<(typeof definition)['id'], typeof definition>;
interface WeatherTileProps { interface WeatherTileProps {
widget: IWeatherWidget; widget: IWeatherWidget;

11
turbo.json Normal file
View File

@@ -0,0 +1,11 @@
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
"outputs": [
".next/**",
"!.next/cache/**"
]
}
}
}

185
yarn.lock
View File

@@ -1272,10 +1272,10 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@next/env@npm:13.1.6": "@next/env@npm:13.2.1":
version: 13.1.6 version: 13.2.1
resolution: "@next/env@npm:13.1.6" resolution: "@next/env@npm:13.2.1"
checksum: 0f911a18f0b3372007632fffa87f5d7f802c00d07b3bf757d2d09574735ae43f60000ecdf64b6f06e195971c508c2bcee82dd1e3aab27a08a4300eb0317652bb checksum: 16a877479348b9d6a9e69e74312546889d6419a6dec0556cf7d9ed5876b4f69a0974c804f2c5ec81526522c243d97bd2d6919d3241cd165e10e8fd6c3bb4b975
languageName: node languageName: node
linkType: hard linkType: hard
@@ -1288,93 +1288,93 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@next/swc-android-arm-eabi@npm:13.1.6": "@next/swc-android-arm-eabi@npm:13.2.1":
version: 13.1.6 version: 13.2.1
resolution: "@next/swc-android-arm-eabi@npm:13.1.6" resolution: "@next/swc-android-arm-eabi@npm:13.2.1"
conditions: os=android & cpu=arm conditions: os=android & cpu=arm
languageName: node languageName: node
linkType: hard linkType: hard
"@next/swc-android-arm64@npm:13.1.6": "@next/swc-android-arm64@npm:13.2.1":
version: 13.1.6 version: 13.2.1
resolution: "@next/swc-android-arm64@npm:13.1.6" resolution: "@next/swc-android-arm64@npm:13.2.1"
conditions: os=android & cpu=arm64 conditions: os=android & cpu=arm64
languageName: node languageName: node
linkType: hard linkType: hard
"@next/swc-darwin-arm64@npm:13.1.6": "@next/swc-darwin-arm64@npm:13.2.1":
version: 13.1.6 version: 13.2.1
resolution: "@next/swc-darwin-arm64@npm:13.1.6" resolution: "@next/swc-darwin-arm64@npm:13.2.1"
conditions: os=darwin & cpu=arm64 conditions: os=darwin & cpu=arm64
languageName: node languageName: node
linkType: hard linkType: hard
"@next/swc-darwin-x64@npm:13.1.6": "@next/swc-darwin-x64@npm:13.2.1":
version: 13.1.6 version: 13.2.1
resolution: "@next/swc-darwin-x64@npm:13.1.6" resolution: "@next/swc-darwin-x64@npm:13.2.1"
conditions: os=darwin & cpu=x64 conditions: os=darwin & cpu=x64
languageName: node languageName: node
linkType: hard linkType: hard
"@next/swc-freebsd-x64@npm:13.1.6": "@next/swc-freebsd-x64@npm:13.2.1":
version: 13.1.6 version: 13.2.1
resolution: "@next/swc-freebsd-x64@npm:13.1.6" resolution: "@next/swc-freebsd-x64@npm:13.2.1"
conditions: os=freebsd & cpu=x64 conditions: os=freebsd & cpu=x64
languageName: node languageName: node
linkType: hard linkType: hard
"@next/swc-linux-arm-gnueabihf@npm:13.1.6": "@next/swc-linux-arm-gnueabihf@npm:13.2.1":
version: 13.1.6 version: 13.2.1
resolution: "@next/swc-linux-arm-gnueabihf@npm:13.1.6" resolution: "@next/swc-linux-arm-gnueabihf@npm:13.2.1"
conditions: os=linux & cpu=arm conditions: os=linux & cpu=arm
languageName: node languageName: node
linkType: hard linkType: hard
"@next/swc-linux-arm64-gnu@npm:13.1.6": "@next/swc-linux-arm64-gnu@npm:13.2.1":
version: 13.1.6 version: 13.2.1
resolution: "@next/swc-linux-arm64-gnu@npm:13.1.6" resolution: "@next/swc-linux-arm64-gnu@npm:13.2.1"
conditions: os=linux & cpu=arm64 & libc=glibc conditions: os=linux & cpu=arm64 & libc=glibc
languageName: node languageName: node
linkType: hard linkType: hard
"@next/swc-linux-arm64-musl@npm:13.1.6": "@next/swc-linux-arm64-musl@npm:13.2.1":
version: 13.1.6 version: 13.2.1
resolution: "@next/swc-linux-arm64-musl@npm:13.1.6" resolution: "@next/swc-linux-arm64-musl@npm:13.2.1"
conditions: os=linux & cpu=arm64 & libc=musl conditions: os=linux & cpu=arm64 & libc=musl
languageName: node languageName: node
linkType: hard linkType: hard
"@next/swc-linux-x64-gnu@npm:13.1.6": "@next/swc-linux-x64-gnu@npm:13.2.1":
version: 13.1.6 version: 13.2.1
resolution: "@next/swc-linux-x64-gnu@npm:13.1.6" resolution: "@next/swc-linux-x64-gnu@npm:13.2.1"
conditions: os=linux & cpu=x64 & libc=glibc conditions: os=linux & cpu=x64 & libc=glibc
languageName: node languageName: node
linkType: hard linkType: hard
"@next/swc-linux-x64-musl@npm:13.1.6": "@next/swc-linux-x64-musl@npm:13.2.1":
version: 13.1.6 version: 13.2.1
resolution: "@next/swc-linux-x64-musl@npm:13.1.6" resolution: "@next/swc-linux-x64-musl@npm:13.2.1"
conditions: os=linux & cpu=x64 & libc=musl conditions: os=linux & cpu=x64 & libc=musl
languageName: node languageName: node
linkType: hard linkType: hard
"@next/swc-win32-arm64-msvc@npm:13.1.6": "@next/swc-win32-arm64-msvc@npm:13.2.1":
version: 13.1.6 version: 13.2.1
resolution: "@next/swc-win32-arm64-msvc@npm:13.1.6" resolution: "@next/swc-win32-arm64-msvc@npm:13.2.1"
conditions: os=win32 & cpu=arm64 conditions: os=win32 & cpu=arm64
languageName: node languageName: node
linkType: hard linkType: hard
"@next/swc-win32-ia32-msvc@npm:13.1.6": "@next/swc-win32-ia32-msvc@npm:13.2.1":
version: 13.1.6 version: 13.2.1
resolution: "@next/swc-win32-ia32-msvc@npm:13.1.6" resolution: "@next/swc-win32-ia32-msvc@npm:13.2.1"
conditions: os=win32 & cpu=ia32 conditions: os=win32 & cpu=ia32
languageName: node languageName: node
linkType: hard linkType: hard
"@next/swc-win32-x64-msvc@npm:13.1.6": "@next/swc-win32-x64-msvc@npm:13.2.1":
version: 13.1.6 version: 13.2.1
resolution: "@next/swc-win32-x64-msvc@npm:13.1.6" resolution: "@next/swc-win32-x64-msvc@npm:13.2.1"
conditions: os=win32 & cpu=x64 conditions: os=win32 & cpu=x64
languageName: node languageName: node
linkType: hard linkType: hard
@@ -4990,7 +4990,7 @@ __metadata:
i18next: ^21.9.1 i18next: ^21.9.1
jest: ^28.1.3 jest: ^28.1.3
js-file-download: ^0.4.12 js-file-download: ^0.4.12
next: ^13.1.6 next: ^13.2.1
next-i18next: ^11.3.0 next-i18next: ^11.3.0
nzbget-api: ^0.0.3 nzbget-api: ^0.0.3
prettier: ^2.7.1 prettier: ^2.7.1
@@ -5001,7 +5001,7 @@ __metadata:
rss-parser: ^3.12.0 rss-parser: ^3.12.0
sabnzbd-api: ^1.5.0 sabnzbd-api: ^1.5.0
sass: ^1.56.1 sass: ^1.56.1
turbo: ^1.7.4 turbo: ^1.8.3
typescript: ^4.7.4 typescript: ^4.7.4
uuid: ^8.3.2 uuid: ^8.3.2
video.js: ^8.0.3 video.js: ^8.0.3
@@ -6697,29 +6697,30 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"next@npm:^13.1.6": "next@npm:^13.2.1":
version: 13.1.6 version: 13.2.1
resolution: "next@npm:13.1.6" resolution: "next@npm:13.2.1"
dependencies: dependencies:
"@next/env": 13.1.6 "@next/env": 13.2.1
"@next/swc-android-arm-eabi": 13.1.6 "@next/swc-android-arm-eabi": 13.2.1
"@next/swc-android-arm64": 13.1.6 "@next/swc-android-arm64": 13.2.1
"@next/swc-darwin-arm64": 13.1.6 "@next/swc-darwin-arm64": 13.2.1
"@next/swc-darwin-x64": 13.1.6 "@next/swc-darwin-x64": 13.2.1
"@next/swc-freebsd-x64": 13.1.6 "@next/swc-freebsd-x64": 13.2.1
"@next/swc-linux-arm-gnueabihf": 13.1.6 "@next/swc-linux-arm-gnueabihf": 13.2.1
"@next/swc-linux-arm64-gnu": 13.1.6 "@next/swc-linux-arm64-gnu": 13.2.1
"@next/swc-linux-arm64-musl": 13.1.6 "@next/swc-linux-arm64-musl": 13.2.1
"@next/swc-linux-x64-gnu": 13.1.6 "@next/swc-linux-x64-gnu": 13.2.1
"@next/swc-linux-x64-musl": 13.1.6 "@next/swc-linux-x64-musl": 13.2.1
"@next/swc-win32-arm64-msvc": 13.1.6 "@next/swc-win32-arm64-msvc": 13.2.1
"@next/swc-win32-ia32-msvc": 13.1.6 "@next/swc-win32-ia32-msvc": 13.2.1
"@next/swc-win32-x64-msvc": 13.1.6 "@next/swc-win32-x64-msvc": 13.2.1
"@swc/helpers": 0.4.14 "@swc/helpers": 0.4.14
caniuse-lite: ^1.0.30001406 caniuse-lite: ^1.0.30001406
postcss: 8.4.14 postcss: 8.4.14
styled-jsx: 5.1.1 styled-jsx: 5.1.1
peerDependencies: peerDependencies:
"@opentelemetry/api": ^1.4.0
fibers: ">= 3.1.0" fibers: ">= 3.1.0"
node-sass: ^6.0.0 || ^7.0.0 node-sass: ^6.0.0 || ^7.0.0
react: ^18.2.0 react: ^18.2.0
@@ -6753,6 +6754,8 @@ __metadata:
"@next/swc-win32-x64-msvc": "@next/swc-win32-x64-msvc":
optional: true optional: true
peerDependenciesMeta: peerDependenciesMeta:
"@opentelemetry/api":
optional: true
fibers: fibers:
optional: true optional: true
node-sass: node-sass:
@@ -6761,7 +6764,7 @@ __metadata:
optional: true optional: true
bin: bin:
next: dist/bin/next next: dist/bin/next
checksum: 584977e382bd826c21e7fc5f67bca50e4d95741a854b1686394d45331404479c7266569671227421975fc18e5cf70769a4ad7edede7450d4497213205bba77c8 checksum: 2dba145ef4d604cd8eadc27f9e5a537df799614d1a801b9161a997f77a432684871eae51642580972a80ef363d724789677ae7c5fe44dc3dd66e71cd43f609c8
languageName: node languageName: node
linkType: hard linkType: hard
@@ -8363,58 +8366,58 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"turbo-darwin-64@npm:1.8.0": "turbo-darwin-64@npm:1.8.3":
version: 1.8.0 version: 1.8.3
resolution: "turbo-darwin-64@npm:1.8.0" resolution: "turbo-darwin-64@npm:1.8.3"
conditions: os=darwin & cpu=x64 conditions: os=darwin & cpu=x64
languageName: node languageName: node
linkType: hard linkType: hard
"turbo-darwin-arm64@npm:1.8.0": "turbo-darwin-arm64@npm:1.8.3":
version: 1.8.0 version: 1.8.3
resolution: "turbo-darwin-arm64@npm:1.8.0" resolution: "turbo-darwin-arm64@npm:1.8.3"
conditions: os=darwin & cpu=arm64 conditions: os=darwin & cpu=arm64
languageName: node languageName: node
linkType: hard linkType: hard
"turbo-linux-64@npm:1.8.0": "turbo-linux-64@npm:1.8.3":
version: 1.8.0 version: 1.8.3
resolution: "turbo-linux-64@npm:1.8.0" resolution: "turbo-linux-64@npm:1.8.3"
conditions: os=linux & cpu=x64 conditions: os=linux & cpu=x64
languageName: node languageName: node
linkType: hard linkType: hard
"turbo-linux-arm64@npm:1.8.0": "turbo-linux-arm64@npm:1.8.3":
version: 1.8.0 version: 1.8.3
resolution: "turbo-linux-arm64@npm:1.8.0" resolution: "turbo-linux-arm64@npm:1.8.3"
conditions: os=linux & cpu=arm64 conditions: os=linux & cpu=arm64
languageName: node languageName: node
linkType: hard linkType: hard
"turbo-windows-64@npm:1.8.0": "turbo-windows-64@npm:1.8.3":
version: 1.8.0 version: 1.8.3
resolution: "turbo-windows-64@npm:1.8.0" resolution: "turbo-windows-64@npm:1.8.3"
conditions: os=win32 & cpu=x64 conditions: os=win32 & cpu=x64
languageName: node languageName: node
linkType: hard linkType: hard
"turbo-windows-arm64@npm:1.8.0": "turbo-windows-arm64@npm:1.8.3":
version: 1.8.0 version: 1.8.3
resolution: "turbo-windows-arm64@npm:1.8.0" resolution: "turbo-windows-arm64@npm:1.8.3"
conditions: os=win32 & cpu=arm64 conditions: os=win32 & cpu=arm64
languageName: node languageName: node
linkType: hard linkType: hard
"turbo@npm:^1.7.4": "turbo@npm:^1.8.3":
version: 1.8.0 version: 1.8.3
resolution: "turbo@npm:1.8.0" resolution: "turbo@npm:1.8.3"
dependencies: dependencies:
turbo-darwin-64: 1.8.0 turbo-darwin-64: 1.8.3
turbo-darwin-arm64: 1.8.0 turbo-darwin-arm64: 1.8.3
turbo-linux-64: 1.8.0 turbo-linux-64: 1.8.3
turbo-linux-arm64: 1.8.0 turbo-linux-arm64: 1.8.3
turbo-windows-64: 1.8.0 turbo-windows-64: 1.8.3
turbo-windows-arm64: 1.8.0 turbo-windows-arm64: 1.8.3
dependenciesMeta: dependenciesMeta:
turbo-darwin-64: turbo-darwin-64:
optional: true optional: true
@@ -8430,7 +8433,7 @@ __metadata:
optional: true optional: true
bin: bin:
turbo: bin/turbo turbo: bin/turbo
checksum: 7f97068d7f9a155e088d3575b1f9922e68fa3015aae0c92625238d44b4e6c275bec2a281907702dedb402fca29a6cd4690499e916cb334d7c24c98099bc3d8b0 checksum: 4a07d120ef8adf6c8e58a48abd02e075ffa215287cc6c3ef843d4fb08aeb0a566fe810ec9bfc376254468a2aa4f29bae154a60804a83af78dfa86d0e8e995476
languageName: node languageName: node
linkType: hard linkType: hard