mirror of
https://github.com/ajnart/homarr.git
synced 2025-11-11 16:05:47 +01:00
Merge branch 'dev' into edit-mode-password
This commit is contained in:
19
.github/ISSUE_TEMPLATE/bug.yml
vendored
19
.github/ISSUE_TEMPLATE/bug.yml
vendored
@@ -32,8 +32,15 @@ body:
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Additional info
|
||||
description: Logs? Screenshots? More info?
|
||||
label: Logs
|
||||
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:
|
||||
required: false
|
||||
- type: checkboxes
|
||||
@@ -42,9 +49,11 @@ body:
|
||||
label: Please tick the boxes
|
||||
description: Before submitting, please ensure that
|
||||
options:
|
||||
- label: You've read the [docs](https://github.com/ajnart/homarr#readme)
|
||||
- label: I confirm that I attached the proper logs
|
||||
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
|
||||
- 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
|
||||
|
||||
7
.github/workflows/docker_dev.yml
vendored
7
.github/workflows/docker_dev.yml
vendored
@@ -24,11 +24,14 @@ env:
|
||||
REGISTRY: ghcr.io
|
||||
# github.repository as <account>/<repo>
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
|
||||
|
||||
jobs:
|
||||
# Push image to GitHub Packages.
|
||||
# See also https://docs.docker.com/docker-hub/builds/
|
||||
yarn_install_and_build:
|
||||
yarn_install_and_build_dev:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
packages: write
|
||||
@@ -67,7 +70,7 @@ jobs:
|
||||
|
||||
- run: yarn install --immutable
|
||||
|
||||
- run: yarn build
|
||||
- run: yarn turbo build
|
||||
|
||||
- name: Docker meta
|
||||
if: github.event_name != 'pull_request'
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -32,6 +32,7 @@ yarn-error.log*
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
.turbo
|
||||
*.tsbuildinfo
|
||||
|
||||
# storybook
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"turbo" : "turbo run build",
|
||||
"analyze": "ANALYZE=true next build",
|
||||
"start": "next start",
|
||||
"typecheck": "tsc --noEmit",
|
||||
@@ -53,7 +54,7 @@
|
||||
"html-entities": "^2.3.3",
|
||||
"i18next": "^21.9.1",
|
||||
"js-file-download": "^0.4.12",
|
||||
"next": "^13.1.6",
|
||||
"next": "^13.2.1",
|
||||
"next-i18next": "^11.3.0",
|
||||
"nzbget-api": "^0.0.3",
|
||||
"prismjs": "^1.29.0",
|
||||
@@ -92,7 +93,7 @@
|
||||
"jest": "^28.1.3",
|
||||
"prettier": "^2.7.1",
|
||||
"sass": "^1.56.1",
|
||||
"turbo": "^1.7.4",
|
||||
"turbo": "^1.8.3",
|
||||
"typescript": "^4.7.4",
|
||||
"video.js": "^8.0.3"
|
||||
},
|
||||
|
||||
@@ -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.",
|
||||
"contact": "Having trouble or questions? Connect with us!",
|
||||
"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": {
|
||||
"configurationSchemaVersion": "Configuration schema version",
|
||||
"configurationsCount": "Available configurations",
|
||||
|
||||
14
public/locales/en/widgets/error-boundary.json
Normal file
14
public/locales/en/widgets/error-boundary.json
Normal 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"
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
import {
|
||||
Accordion,
|
||||
ActionIcon,
|
||||
Anchor,
|
||||
Badge,
|
||||
Button,
|
||||
createStyles,
|
||||
Divider,
|
||||
Grid,
|
||||
Group,
|
||||
HoverCard,
|
||||
Kbd,
|
||||
Modal,
|
||||
Table,
|
||||
Text,
|
||||
@@ -36,6 +37,7 @@ import { useConfigStore } from '../../../../config/store';
|
||||
import { useEditModeInformationStore } from '../../../../hooks/useEditModeInformation';
|
||||
import { usePackageAttributesStore } from '../../../../tools/client/zustands/usePackageAttributesStore';
|
||||
import { useColorTheme } from '../../../../tools/color';
|
||||
import Tip from '../../../layout/Tip';
|
||||
import { usePrimaryGradient } from '../../../layout/useGradient';
|
||||
import Credits from '../../../Settings/Common/Credits';
|
||||
|
||||
@@ -51,6 +53,23 @@ export const AboutModal = ({ opened, closeModal, newVersionAvailable }: AboutMod
|
||||
const informations = useInformationTableItems(newVersionAvailable);
|
||||
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 (
|
||||
<Modal
|
||||
onClose={() => closeModal()}
|
||||
@@ -77,7 +96,7 @@ export const AboutModal = ({ opened, closeModal, newVersionAvailable }: AboutMod
|
||||
<Trans i18nKey="layout/modals/about:description" />
|
||||
</Text>
|
||||
|
||||
<Table mb="lg" striped highlightOnHover withBorder>
|
||||
<Table mb="lg" highlightOnHover withBorder>
|
||||
<tbody>
|
||||
{informations.map((item, index) => (
|
||||
<tr key={index}>
|
||||
@@ -101,8 +120,26 @@ export const AboutModal = ({ opened, closeModal, newVersionAvailable }: AboutMod
|
||||
))}
|
||||
</tbody>
|
||||
</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">
|
||||
{t('layout/modals/about:contact')}
|
||||
</Title>
|
||||
|
||||
@@ -90,7 +90,8 @@ export const IconSelector = ({
|
||||
}
|
||||
variant="default"
|
||||
withAsterisk
|
||||
dropdownComponent={(props: any) => <ScrollArea {...props} mah={400} />}
|
||||
dropdownComponent={(props: any) => <ScrollArea {...props} mah={250} />}
|
||||
dropdownPosition="bottom"
|
||||
required
|
||||
onChange={(event) => {
|
||||
if (allowAppNamePropagation) {
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
Grid,
|
||||
Group,
|
||||
PasswordInput,
|
||||
Stack,
|
||||
ThemeIcon,
|
||||
Title,
|
||||
Text,
|
||||
@@ -40,7 +39,7 @@ export const GenericSecretInput = ({
|
||||
|
||||
const Icon = setIcon;
|
||||
|
||||
const [displayUpdateField, setDisplayUpdateField] = useState<boolean>(false);
|
||||
const [displayUpdateField, setDisplayUpdateField] = useState<boolean>(!secretIsPresent);
|
||||
const { t } = useTranslation(['layout/modals/add-app', 'common']);
|
||||
|
||||
return (
|
||||
@@ -51,26 +50,26 @@ export const GenericSecretInput = ({
|
||||
<ThemeIcon color={secretIsPresent ? 'green' : 'red'} variant="light" size="lg">
|
||||
<Icon size={18} />
|
||||
</ThemeIcon>
|
||||
<Stack spacing={0}>
|
||||
<Flex justify="start" align="start" direction="column">
|
||||
<Group spacing="xs">
|
||||
<Title className={classes.subtitle} order={6}>
|
||||
{t(label)}
|
||||
</Title>
|
||||
|
||||
<Group spacing="xs">
|
||||
{secretIsPresent ? (
|
||||
<Badge className={classes.textTransformUnset} color="green" variant="dot">
|
||||
{t('integration.type.defined')}
|
||||
<Badge
|
||||
className={classes.textTransformUnset}
|
||||
color={secretIsPresent ? 'green' : 'red'}
|
||||
variant="dot"
|
||||
>
|
||||
{secretIsPresent
|
||||
? t('integration.type.defined')
|
||||
: t('integration.type.undefined')}
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge className={classes.textTransformUnset} color="red" variant="dot">
|
||||
{t('integration.type.undefined')}
|
||||
</Badge>
|
||||
)}
|
||||
{type === 'private' ? (
|
||||
<Tooltip
|
||||
label={t('integration.type.explanationPrivate')}
|
||||
width={200}
|
||||
width={400}
|
||||
multiline
|
||||
withinPortal
|
||||
withArrow
|
||||
@@ -82,7 +81,7 @@ export const GenericSecretInput = ({
|
||||
) : (
|
||||
<Tooltip
|
||||
label={t('integration.type.explanationPublic')}
|
||||
width={200}
|
||||
width={400}
|
||||
multiline
|
||||
withinPortal
|
||||
withArrow
|
||||
@@ -94,29 +93,20 @@ export const GenericSecretInput = ({
|
||||
)}
|
||||
</Group>
|
||||
</Group>
|
||||
<Text size="xs" color="dimmed">
|
||||
<Text size="xs" color="dimmed" w={400}>
|
||||
{type === 'private'
|
||||
? 'Private: Once saved, you cannot read out this value again'
|
||||
: 'Public: Can be read out repeatedly'}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Flex>
|
||||
</Group>
|
||||
</Grid.Col>
|
||||
<Grid.Col xs={12} md={6}>
|
||||
<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 ? (
|
||||
<PasswordInput
|
||||
required
|
||||
defaultValue={value}
|
||||
placeholder="new secret"
|
||||
styles={{ root: { width: 200 } }}
|
||||
{...props}
|
||||
|
||||
@@ -10,6 +10,9 @@ interface NetworkTabProps {
|
||||
|
||||
export const NetworkTab = ({ form }: NetworkTabProps) => {
|
||||
const { t } = useTranslation('layout/modals/add-app');
|
||||
const acceptableStatusCodes = (form.values.network.statusCodes ?? ['200']).map((x) =>
|
||||
x.toString()
|
||||
);
|
||||
return (
|
||||
<Tabs.Panel value="network" pt="lg">
|
||||
<Switch
|
||||
@@ -27,7 +30,7 @@ export const NetworkTab = ({ form }: NetworkTabProps) => {
|
||||
data={StatusCodes}
|
||||
clearable
|
||||
searchable
|
||||
defaultValue={form.values.network.okStatus.map((x) => `${x}`)}
|
||||
defaultValue={acceptableStatusCodes}
|
||||
variant="default"
|
||||
{...form.getInputProps('network.statusCodes')}
|
||||
/>
|
||||
|
||||
@@ -95,7 +95,7 @@ export const AvailableElementTypes = ({
|
||||
},
|
||||
network: {
|
||||
enabledStatusChecker: true,
|
||||
okStatus: [200],
|
||||
statusCodes: ['200'],
|
||||
},
|
||||
behaviour: {
|
||||
isOpeningNewTab: true,
|
||||
|
||||
@@ -19,7 +19,7 @@ export const AppPing = ({ app }: AppPingProps) => {
|
||||
queryKey: ['ping', { id: app.id, name: app.name }],
|
||||
queryFn: async () => {
|
||||
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 {
|
||||
status: response.status,
|
||||
state: isOk ? 'online' : 'down',
|
||||
@@ -60,5 +60,3 @@ export const AppPing = ({ app }: AppPingProps) => {
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
|
||||
type PingState = 'loading' | 'down' | 'online';
|
||||
|
||||
@@ -36,7 +36,13 @@ export const AppTile = ({ className, app }: AppTileProps) => {
|
||||
className="dashboard-tile-app"
|
||||
>
|
||||
<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}
|
||||
</Title>
|
||||
</Box>
|
||||
|
||||
@@ -24,9 +24,9 @@ export const GenericTileMenu = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<Menu withinPortal withArrow position="right-start">
|
||||
<Menu withinPortal withArrow position="right">
|
||||
<Menu.Target>
|
||||
<ActionIcon pos="absolute" top={4} right={4}>
|
||||
<ActionIcon size="md" radius="md" variant="light" pos="absolute" top={8} right={8}>
|
||||
<IconDots />
|
||||
</ActionIcon>
|
||||
</Menu.Target>
|
||||
|
||||
@@ -10,9 +10,7 @@ export default function CustomizationSettings() {
|
||||
return (
|
||||
<ScrollArea style={{ height: height - 100 }} offsetScrollbars>
|
||||
<Stack mt="xs" mb="md" spacing="xs">
|
||||
<Text color="dimmed">
|
||||
{t('text')}
|
||||
</Text>
|
||||
<Text color="dimmed">{t('text')}</Text>
|
||||
<CustomizationSettingsAccordeon />
|
||||
</Stack>
|
||||
</ScrollArea>
|
||||
|
||||
@@ -8,7 +8,9 @@ export const LogoImageChanger = () => {
|
||||
const { t } = useTranslation('settings/customization/page-appearance');
|
||||
const updateConfig = useConfigStore((x) => x.updateConfig);
|
||||
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;
|
||||
|
||||
|
||||
@@ -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 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 { Trans, useTranslation } from 'next-i18next';
|
||||
import { useHotkeys } from '@mantine/hooks';
|
||||
import { hideNotification, showNotification } from '@mantine/notifications';
|
||||
import { useConfigContext } from '../../../../../config/provider';
|
||||
import { useScreenSmallerThan } from '../../../../../hooks/useScreenSmallerThan';
|
||||
|
||||
import { useEditModeStore } from '../../../../Dashboard/Views/useEditModeStore';
|
||||
import { AddElementAction } from '../AddElementAction/AddElementAction';
|
||||
import { useNamedWrapperColumnCount } from '../../../../Dashboard/Wrappers/gridstack/store';
|
||||
import { useCardStyles } from '../../../useCardStyles';
|
||||
import { AddElementAction } from '../AddElementAction/AddElementAction';
|
||||
|
||||
const beforeUnloadEventText = 'Exit the edit mode to save your changes';
|
||||
|
||||
export const ToggleEditModeAction = () => {
|
||||
const { enabled, toggleEditMode } = useEditModeStore();
|
||||
@@ -27,7 +29,17 @@ export const ToggleEditModeAction = () => {
|
||||
const { config } = useConfigContext();
|
||||
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 = () => {
|
||||
toggleEditMode();
|
||||
|
||||
@@ -10,5 +10,7 @@ export const useGetDashboardIcons = () =>
|
||||
return data as NormalizedIconRepositoryResult[];
|
||||
},
|
||||
refetchOnMount: false,
|
||||
// Cache for infinity, refetch every so often.
|
||||
cacheTime: Infinity,
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { NormalizedDownloadQueueResponse } from '../../../types/api/downloads/queue/NormalizedDownloadQueueResponse';
|
||||
|
||||
export const useGetDownloadClientsQueue = () => useQuery({
|
||||
export const useGetDownloadClientsQueue = () =>
|
||||
useQuery({
|
||||
queryKey: ['network-speed'],
|
||||
queryFn: async (): Promise<NormalizedDownloadQueueResponse> => {
|
||||
const response = await fetch('/api/modules/downloads');
|
||||
|
||||
@@ -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, ev: NextFetchEvent) {
|
||||
export function middleware(req: NextRequest) {
|
||||
const { cookies } = req;
|
||||
|
||||
// Don't even bother with the middleware if there is no defined password
|
||||
if (!process.env.PASSWORD) return NextResponse.next();
|
||||
|
||||
|
||||
@@ -177,7 +177,7 @@ export default function ContainerActionBar({ selected, reload }: ContainerAction
|
||||
},
|
||||
network: {
|
||||
enabledStatusChecker: true,
|
||||
okStatus: [200],
|
||||
statusCodes: ['200'],
|
||||
},
|
||||
behaviour: {
|
||||
isOpeningNewTab: true,
|
||||
|
||||
@@ -38,6 +38,7 @@ function App(
|
||||
colorScheme: ColorScheme;
|
||||
packageAttributes: ServerSidePackageAttributesType;
|
||||
editModeEnabled: boolean;
|
||||
defaultColorScheme: ColorScheme;
|
||||
}
|
||||
) {
|
||||
const { Component, pageProps } = props;
|
||||
@@ -55,7 +56,7 @@ function App(
|
||||
|
||||
// hook will return either 'dark' or 'light' on client
|
||||
// and always 'light' during ssr as window.matchMedia is not available
|
||||
const preferredColorScheme = useColorScheme();
|
||||
const preferredColorScheme = useColorScheme(props.defaultColorScheme);
|
||||
const [colorScheme, setColorScheme] = useLocalStorage<ColorScheme>({
|
||||
key: 'mantine-color-scheme',
|
||||
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'
|
||||
);
|
||||
}
|
||||
|
||||
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 {
|
||||
colorScheme: getCookie('color-scheme', ctx) || 'light',
|
||||
packageAttributes: getServiceSidePackageAttributes(),
|
||||
editModeEnabled: !disableEditMode,
|
||||
defaultColorScheme: colorScheme,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -6,10 +6,26 @@ import { UnpkgIconsRepository } from '../../../tools/server/images/unpkg-icons-r
|
||||
const Get = async (request: NextApiRequest, response: NextApiResponse) => {
|
||||
const respositories = [
|
||||
new LocalIconsRepository(),
|
||||
new JsdelivrIconsRepository(JsdelivrIconsRepository.tablerRepository, 'Walkxcode Dashboard Icons', '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)'),
|
||||
new JsdelivrIconsRepository(
|
||||
JsdelivrIconsRepository.tablerRepository,
|
||||
'Walkxcode Dashboard Icons',
|
||||
'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 data = await Promise.all(fetches);
|
||||
|
||||
@@ -61,7 +61,9 @@ const Get = async (request: NextApiRequest, response: NextApiResponse) => {
|
||||
const responseBody = { apps: data, failedApps: failedClients } as NormalizedDownloadQueueResponse;
|
||||
|
||||
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);
|
||||
|
||||
@@ -53,6 +53,7 @@ export const Get = async (request: NextApiRequest, response: NextApiResponse) =>
|
||||
title: item.title ? decode(item.title) : undefined,
|
||||
content: decode(item.content),
|
||||
enclosure: createEnclosure(item),
|
||||
link: createLink(item),
|
||||
}))
|
||||
.sort((a: { pubDate: number }, b: { pubDate: number }) => {
|
||||
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) => {
|
||||
if (item.enclosure) {
|
||||
return item.enclosure;
|
||||
|
||||
@@ -159,7 +159,7 @@ const migrateService = (oldService: serviceItem, areaType: AreaType): ConfigAppT
|
||||
},
|
||||
network: {
|
||||
enabledStatusChecker: oldService.ping ?? true,
|
||||
okStatus: oldService.status?.map((str) => parseInt(str, 10)) ?? [200],
|
||||
statusCodes: oldService.status ?? ['200'],
|
||||
},
|
||||
appearance: {
|
||||
iconUrl: migrateIcon(oldService.icon),
|
||||
|
||||
@@ -23,7 +23,7 @@ export class JsdelivrIconsRepository extends AbstractIconRepository {
|
||||
constructor(
|
||||
private readonly repository: JsdelivrRepositoryUrl,
|
||||
private readonly displayName: string,
|
||||
copyright: string,
|
||||
copyright: string
|
||||
) {
|
||||
super(copyright);
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ export const dashboardNamespaces = [
|
||||
'modules/media-server',
|
||||
'modules/common-media-cards',
|
||||
'modules/video-stream',
|
||||
'widgets/error-boundary',
|
||||
];
|
||||
|
||||
export const loginNamespaces = ['authentication/login'];
|
||||
|
||||
@@ -14,18 +14,23 @@ export type GenericCurrentlyPlaying = {
|
||||
episodeCount: number | undefined;
|
||||
type: 'audio' | 'video' | 'tv' | 'movie' | undefined;
|
||||
metadata: {
|
||||
video: {
|
||||
video:
|
||||
| {
|
||||
videoCodec: string | undefined;
|
||||
videoFrameRate: string | undefined;
|
||||
height: number | undefined;
|
||||
width: number | undefined;
|
||||
bitrate: number | undefined;
|
||||
} | undefined;
|
||||
audio: {
|
||||
}
|
||||
| undefined;
|
||||
audio:
|
||||
| {
|
||||
audioCodec: string | undefined;
|
||||
audioChannels: number | undefined;
|
||||
} | undefined;
|
||||
transcoding: {
|
||||
}
|
||||
| undefined;
|
||||
transcoding:
|
||||
| {
|
||||
context: string | undefined;
|
||||
sourceVideoCodec: string | undefined;
|
||||
sourceAudioCodec: string | undefined;
|
||||
@@ -41,6 +46,7 @@ export type GenericCurrentlyPlaying = {
|
||||
height: number | undefined;
|
||||
transcodeHwRequested: boolean | undefined;
|
||||
timeStamp: number | undefined;
|
||||
} | undefined;
|
||||
}
|
||||
| undefined;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -23,7 +23,7 @@ interface AppBehaviourType {
|
||||
|
||||
interface AppNetworkType {
|
||||
enabledStatusChecker: boolean;
|
||||
okStatus: number[];
|
||||
statusCodes: string[];
|
||||
}
|
||||
|
||||
interface AppAppearanceType {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { ComponentType, useMemo } from 'react';
|
||||
import Widgets from '.';
|
||||
import { HomarrCardWrapper } from '../components/Dashboard/Tiles/HomarrCardWrapper';
|
||||
import { WidgetsMenu } from '../components/Dashboard/Tiles/Widgets/WidgetsMenu';
|
||||
import ErrorBoundary from './boundary';
|
||||
import { IWidget } from './widgets';
|
||||
|
||||
interface WidgetWrapperProps {
|
||||
@@ -40,9 +41,11 @@ export const WidgetWrapper = ({
|
||||
const widgetWithDefaultProps = useWidget(widget);
|
||||
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<HomarrCardWrapper className={className}>
|
||||
<WidgetsMenu integration={widgetId} widget={widgetWithDefaultProps} />
|
||||
<WidgetComponent widget={widgetWithDefaultProps} />
|
||||
</HomarrCardWrapper>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
};
|
||||
|
||||
127
src/widgets/boundary.tsx
Normal file
127
src/widgets/boundary.tsx
Normal 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);
|
||||
@@ -25,7 +25,7 @@ const definition = defineWidget({
|
||||
component: DateTile,
|
||||
});
|
||||
|
||||
export type IDateWidget = IWidget<typeof definition['id'], typeof definition>;
|
||||
export type IDateWidget = IWidget<(typeof definition)['id'], typeof definition>;
|
||||
|
||||
interface DateTileProps {
|
||||
widget: IDateWidget;
|
||||
|
||||
@@ -107,7 +107,9 @@ export const DetailCollapseable = ({ session }: { session: GenericSessionInfo })
|
||||
</Group>
|
||||
<Text>{session.sessionName}</Text>
|
||||
</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>
|
||||
{details.map((detail, index) => (
|
||||
<Grid.Col xs={12} sm={6} key={index}>
|
||||
|
||||
@@ -16,7 +16,6 @@ import {
|
||||
Title,
|
||||
UnstyledButton,
|
||||
} from '@mantine/core';
|
||||
import { useElementSize } from '@mantine/hooks';
|
||||
import {
|
||||
IconBulldozer,
|
||||
IconCalendarTime,
|
||||
@@ -65,7 +64,6 @@ function RssTile({ widget }: RssTileProps) {
|
||||
);
|
||||
const { classes } = useStyles();
|
||||
const [loadingOverlayVisible, setLoadingOverlayVisible] = useState(false);
|
||||
const { ref, height } = useElementSize();
|
||||
|
||||
if (!data || isLoading) {
|
||||
return (
|
||||
@@ -88,7 +86,7 @@ function RssTile({ widget }: RssTileProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack ref={ref} h="100%">
|
||||
<Stack h="100%">
|
||||
<LoadingOverlay visible={loadingOverlayVisible} />
|
||||
<Flex gap="md">
|
||||
{data.feed.image ? (
|
||||
@@ -121,7 +119,7 @@ function RssTile({ widget }: RssTileProps) {
|
||||
<Card
|
||||
key={index}
|
||||
withBorder
|
||||
component={Link}
|
||||
component={Link ?? 'div'}
|
||||
href={item.link}
|
||||
radius="md"
|
||||
target="_blank"
|
||||
@@ -137,6 +135,7 @@ function RssTile({ widget }: RssTileProps) {
|
||||
)}
|
||||
|
||||
<Flex gap="xs">
|
||||
{item.enclosure && (
|
||||
<MediaQuery query="(max-width: 1200px)" styles={{ display: 'none' }}>
|
||||
<Image
|
||||
src={item.enclosure?.url ?? undefined}
|
||||
@@ -146,7 +145,8 @@ function RssTile({ widget }: RssTileProps) {
|
||||
withPlaceholder
|
||||
/>
|
||||
</MediaQuery>
|
||||
<Flex gap={2} direction="column">
|
||||
)}
|
||||
<Flex gap={2} direction="column" w="100%">
|
||||
{item.categories && (
|
||||
<Flex gap="xs" wrap="wrap" h={20} style={{ overflow: 'hidden' }}>
|
||||
{item.categories.map((category: any, categoryIndex: number) => (
|
||||
@@ -181,12 +181,14 @@ function RssTile({ widget }: RssTileProps) {
|
||||
{data.feed.pubDate}
|
||||
</Text>
|
||||
</Group>
|
||||
{data.feed.lastBuildDate && (
|
||||
<Group>
|
||||
<IconBulldozer size={14} />
|
||||
<Text color="dimmed" size="sm">
|
||||
{data.feed.lastBuildDate}
|
||||
</Text>
|
||||
</Group>
|
||||
)}
|
||||
{data.feed.feedUrl && (
|
||||
<Group spacing="sm">
|
||||
<IconSpeakerphone size={14} />
|
||||
|
||||
@@ -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 {
|
||||
widget: IUsenetWidget;
|
||||
|
||||
@@ -28,7 +28,7 @@ const definition = defineWidget({
|
||||
component: WeatherTile,
|
||||
});
|
||||
|
||||
export type IWeatherWidget = IWidget<typeof definition['id'], typeof definition>;
|
||||
export type IWeatherWidget = IWidget<(typeof definition)['id'], typeof definition>;
|
||||
|
||||
interface WeatherTileProps {
|
||||
widget: IWeatherWidget;
|
||||
|
||||
11
turbo.json
Normal file
11
turbo.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"$schema": "https://turbo.build/schema.json",
|
||||
"pipeline": {
|
||||
"build": {
|
||||
"outputs": [
|
||||
".next/**",
|
||||
"!.next/cache/**"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
185
yarn.lock
185
yarn.lock
@@ -1272,10 +1272,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/env@npm:13.1.6":
|
||||
version: 13.1.6
|
||||
resolution: "@next/env@npm:13.1.6"
|
||||
checksum: 0f911a18f0b3372007632fffa87f5d7f802c00d07b3bf757d2d09574735ae43f60000ecdf64b6f06e195971c508c2bcee82dd1e3aab27a08a4300eb0317652bb
|
||||
"@next/env@npm:13.2.1":
|
||||
version: 13.2.1
|
||||
resolution: "@next/env@npm:13.2.1"
|
||||
checksum: 16a877479348b9d6a9e69e74312546889d6419a6dec0556cf7d9ed5876b4f69a0974c804f2c5ec81526522c243d97bd2d6919d3241cd165e10e8fd6c3bb4b975
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -1288,93 +1288,93 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-android-arm-eabi@npm:13.1.6":
|
||||
version: 13.1.6
|
||||
resolution: "@next/swc-android-arm-eabi@npm:13.1.6"
|
||||
"@next/swc-android-arm-eabi@npm:13.2.1":
|
||||
version: 13.2.1
|
||||
resolution: "@next/swc-android-arm-eabi@npm:13.2.1"
|
||||
conditions: os=android & cpu=arm
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-android-arm64@npm:13.1.6":
|
||||
version: 13.1.6
|
||||
resolution: "@next/swc-android-arm64@npm:13.1.6"
|
||||
"@next/swc-android-arm64@npm:13.2.1":
|
||||
version: 13.2.1
|
||||
resolution: "@next/swc-android-arm64@npm:13.2.1"
|
||||
conditions: os=android & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-darwin-arm64@npm:13.1.6":
|
||||
version: 13.1.6
|
||||
resolution: "@next/swc-darwin-arm64@npm:13.1.6"
|
||||
"@next/swc-darwin-arm64@npm:13.2.1":
|
||||
version: 13.2.1
|
||||
resolution: "@next/swc-darwin-arm64@npm:13.2.1"
|
||||
conditions: os=darwin & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-darwin-x64@npm:13.1.6":
|
||||
version: 13.1.6
|
||||
resolution: "@next/swc-darwin-x64@npm:13.1.6"
|
||||
"@next/swc-darwin-x64@npm:13.2.1":
|
||||
version: 13.2.1
|
||||
resolution: "@next/swc-darwin-x64@npm:13.2.1"
|
||||
conditions: os=darwin & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-freebsd-x64@npm:13.1.6":
|
||||
version: 13.1.6
|
||||
resolution: "@next/swc-freebsd-x64@npm:13.1.6"
|
||||
"@next/swc-freebsd-x64@npm:13.2.1":
|
||||
version: 13.2.1
|
||||
resolution: "@next/swc-freebsd-x64@npm:13.2.1"
|
||||
conditions: os=freebsd & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-linux-arm-gnueabihf@npm:13.1.6":
|
||||
version: 13.1.6
|
||||
resolution: "@next/swc-linux-arm-gnueabihf@npm:13.1.6"
|
||||
"@next/swc-linux-arm-gnueabihf@npm:13.2.1":
|
||||
version: 13.2.1
|
||||
resolution: "@next/swc-linux-arm-gnueabihf@npm:13.2.1"
|
||||
conditions: os=linux & cpu=arm
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-linux-arm64-gnu@npm:13.1.6":
|
||||
version: 13.1.6
|
||||
resolution: "@next/swc-linux-arm64-gnu@npm:13.1.6"
|
||||
"@next/swc-linux-arm64-gnu@npm:13.2.1":
|
||||
version: 13.2.1
|
||||
resolution: "@next/swc-linux-arm64-gnu@npm:13.2.1"
|
||||
conditions: os=linux & cpu=arm64 & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-linux-arm64-musl@npm:13.1.6":
|
||||
version: 13.1.6
|
||||
resolution: "@next/swc-linux-arm64-musl@npm:13.1.6"
|
||||
"@next/swc-linux-arm64-musl@npm:13.2.1":
|
||||
version: 13.2.1
|
||||
resolution: "@next/swc-linux-arm64-musl@npm:13.2.1"
|
||||
conditions: os=linux & cpu=arm64 & libc=musl
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-linux-x64-gnu@npm:13.1.6":
|
||||
version: 13.1.6
|
||||
resolution: "@next/swc-linux-x64-gnu@npm:13.1.6"
|
||||
"@next/swc-linux-x64-gnu@npm:13.2.1":
|
||||
version: 13.2.1
|
||||
resolution: "@next/swc-linux-x64-gnu@npm:13.2.1"
|
||||
conditions: os=linux & cpu=x64 & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-linux-x64-musl@npm:13.1.6":
|
||||
version: 13.1.6
|
||||
resolution: "@next/swc-linux-x64-musl@npm:13.1.6"
|
||||
"@next/swc-linux-x64-musl@npm:13.2.1":
|
||||
version: 13.2.1
|
||||
resolution: "@next/swc-linux-x64-musl@npm:13.2.1"
|
||||
conditions: os=linux & cpu=x64 & libc=musl
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-win32-arm64-msvc@npm:13.1.6":
|
||||
version: 13.1.6
|
||||
resolution: "@next/swc-win32-arm64-msvc@npm:13.1.6"
|
||||
"@next/swc-win32-arm64-msvc@npm:13.2.1":
|
||||
version: 13.2.1
|
||||
resolution: "@next/swc-win32-arm64-msvc@npm:13.2.1"
|
||||
conditions: os=win32 & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-win32-ia32-msvc@npm:13.1.6":
|
||||
version: 13.1.6
|
||||
resolution: "@next/swc-win32-ia32-msvc@npm:13.1.6"
|
||||
"@next/swc-win32-ia32-msvc@npm:13.2.1":
|
||||
version: 13.2.1
|
||||
resolution: "@next/swc-win32-ia32-msvc@npm:13.2.1"
|
||||
conditions: os=win32 & cpu=ia32
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@next/swc-win32-x64-msvc@npm:13.1.6":
|
||||
version: 13.1.6
|
||||
resolution: "@next/swc-win32-x64-msvc@npm:13.1.6"
|
||||
"@next/swc-win32-x64-msvc@npm:13.2.1":
|
||||
version: 13.2.1
|
||||
resolution: "@next/swc-win32-x64-msvc@npm:13.2.1"
|
||||
conditions: os=win32 & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
@@ -4990,7 +4990,7 @@ __metadata:
|
||||
i18next: ^21.9.1
|
||||
jest: ^28.1.3
|
||||
js-file-download: ^0.4.12
|
||||
next: ^13.1.6
|
||||
next: ^13.2.1
|
||||
next-i18next: ^11.3.0
|
||||
nzbget-api: ^0.0.3
|
||||
prettier: ^2.7.1
|
||||
@@ -5001,7 +5001,7 @@ __metadata:
|
||||
rss-parser: ^3.12.0
|
||||
sabnzbd-api: ^1.5.0
|
||||
sass: ^1.56.1
|
||||
turbo: ^1.7.4
|
||||
turbo: ^1.8.3
|
||||
typescript: ^4.7.4
|
||||
uuid: ^8.3.2
|
||||
video.js: ^8.0.3
|
||||
@@ -6697,29 +6697,30 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"next@npm:^13.1.6":
|
||||
version: 13.1.6
|
||||
resolution: "next@npm:13.1.6"
|
||||
"next@npm:^13.2.1":
|
||||
version: 13.2.1
|
||||
resolution: "next@npm:13.2.1"
|
||||
dependencies:
|
||||
"@next/env": 13.1.6
|
||||
"@next/swc-android-arm-eabi": 13.1.6
|
||||
"@next/swc-android-arm64": 13.1.6
|
||||
"@next/swc-darwin-arm64": 13.1.6
|
||||
"@next/swc-darwin-x64": 13.1.6
|
||||
"@next/swc-freebsd-x64": 13.1.6
|
||||
"@next/swc-linux-arm-gnueabihf": 13.1.6
|
||||
"@next/swc-linux-arm64-gnu": 13.1.6
|
||||
"@next/swc-linux-arm64-musl": 13.1.6
|
||||
"@next/swc-linux-x64-gnu": 13.1.6
|
||||
"@next/swc-linux-x64-musl": 13.1.6
|
||||
"@next/swc-win32-arm64-msvc": 13.1.6
|
||||
"@next/swc-win32-ia32-msvc": 13.1.6
|
||||
"@next/swc-win32-x64-msvc": 13.1.6
|
||||
"@next/env": 13.2.1
|
||||
"@next/swc-android-arm-eabi": 13.2.1
|
||||
"@next/swc-android-arm64": 13.2.1
|
||||
"@next/swc-darwin-arm64": 13.2.1
|
||||
"@next/swc-darwin-x64": 13.2.1
|
||||
"@next/swc-freebsd-x64": 13.2.1
|
||||
"@next/swc-linux-arm-gnueabihf": 13.2.1
|
||||
"@next/swc-linux-arm64-gnu": 13.2.1
|
||||
"@next/swc-linux-arm64-musl": 13.2.1
|
||||
"@next/swc-linux-x64-gnu": 13.2.1
|
||||
"@next/swc-linux-x64-musl": 13.2.1
|
||||
"@next/swc-win32-arm64-msvc": 13.2.1
|
||||
"@next/swc-win32-ia32-msvc": 13.2.1
|
||||
"@next/swc-win32-x64-msvc": 13.2.1
|
||||
"@swc/helpers": 0.4.14
|
||||
caniuse-lite: ^1.0.30001406
|
||||
postcss: 8.4.14
|
||||
styled-jsx: 5.1.1
|
||||
peerDependencies:
|
||||
"@opentelemetry/api": ^1.4.0
|
||||
fibers: ">= 3.1.0"
|
||||
node-sass: ^6.0.0 || ^7.0.0
|
||||
react: ^18.2.0
|
||||
@@ -6753,6 +6754,8 @@ __metadata:
|
||||
"@next/swc-win32-x64-msvc":
|
||||
optional: true
|
||||
peerDependenciesMeta:
|
||||
"@opentelemetry/api":
|
||||
optional: true
|
||||
fibers:
|
||||
optional: true
|
||||
node-sass:
|
||||
@@ -6761,7 +6764,7 @@ __metadata:
|
||||
optional: true
|
||||
bin:
|
||||
next: dist/bin/next
|
||||
checksum: 584977e382bd826c21e7fc5f67bca50e4d95741a854b1686394d45331404479c7266569671227421975fc18e5cf70769a4ad7edede7450d4497213205bba77c8
|
||||
checksum: 2dba145ef4d604cd8eadc27f9e5a537df799614d1a801b9161a997f77a432684871eae51642580972a80ef363d724789677ae7c5fe44dc3dd66e71cd43f609c8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -8363,58 +8366,58 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"turbo-darwin-64@npm:1.8.0":
|
||||
version: 1.8.0
|
||||
resolution: "turbo-darwin-64@npm:1.8.0"
|
||||
"turbo-darwin-64@npm:1.8.3":
|
||||
version: 1.8.3
|
||||
resolution: "turbo-darwin-64@npm:1.8.3"
|
||||
conditions: os=darwin & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"turbo-darwin-arm64@npm:1.8.0":
|
||||
version: 1.8.0
|
||||
resolution: "turbo-darwin-arm64@npm:1.8.0"
|
||||
"turbo-darwin-arm64@npm:1.8.3":
|
||||
version: 1.8.3
|
||||
resolution: "turbo-darwin-arm64@npm:1.8.3"
|
||||
conditions: os=darwin & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"turbo-linux-64@npm:1.8.0":
|
||||
version: 1.8.0
|
||||
resolution: "turbo-linux-64@npm:1.8.0"
|
||||
"turbo-linux-64@npm:1.8.3":
|
||||
version: 1.8.3
|
||||
resolution: "turbo-linux-64@npm:1.8.3"
|
||||
conditions: os=linux & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"turbo-linux-arm64@npm:1.8.0":
|
||||
version: 1.8.0
|
||||
resolution: "turbo-linux-arm64@npm:1.8.0"
|
||||
"turbo-linux-arm64@npm:1.8.3":
|
||||
version: 1.8.3
|
||||
resolution: "turbo-linux-arm64@npm:1.8.3"
|
||||
conditions: os=linux & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"turbo-windows-64@npm:1.8.0":
|
||||
version: 1.8.0
|
||||
resolution: "turbo-windows-64@npm:1.8.0"
|
||||
"turbo-windows-64@npm:1.8.3":
|
||||
version: 1.8.3
|
||||
resolution: "turbo-windows-64@npm:1.8.3"
|
||||
conditions: os=win32 & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"turbo-windows-arm64@npm:1.8.0":
|
||||
version: 1.8.0
|
||||
resolution: "turbo-windows-arm64@npm:1.8.0"
|
||||
"turbo-windows-arm64@npm:1.8.3":
|
||||
version: 1.8.3
|
||||
resolution: "turbo-windows-arm64@npm:1.8.3"
|
||||
conditions: os=win32 & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"turbo@npm:^1.7.4":
|
||||
version: 1.8.0
|
||||
resolution: "turbo@npm:1.8.0"
|
||||
"turbo@npm:^1.8.3":
|
||||
version: 1.8.3
|
||||
resolution: "turbo@npm:1.8.3"
|
||||
dependencies:
|
||||
turbo-darwin-64: 1.8.0
|
||||
turbo-darwin-arm64: 1.8.0
|
||||
turbo-linux-64: 1.8.0
|
||||
turbo-linux-arm64: 1.8.0
|
||||
turbo-windows-64: 1.8.0
|
||||
turbo-windows-arm64: 1.8.0
|
||||
turbo-darwin-64: 1.8.3
|
||||
turbo-darwin-arm64: 1.8.3
|
||||
turbo-linux-64: 1.8.3
|
||||
turbo-linux-arm64: 1.8.3
|
||||
turbo-windows-64: 1.8.3
|
||||
turbo-windows-arm64: 1.8.3
|
||||
dependenciesMeta:
|
||||
turbo-darwin-64:
|
||||
optional: true
|
||||
@@ -8430,7 +8433,7 @@ __metadata:
|
||||
optional: true
|
||||
bin:
|
||||
turbo: bin/turbo
|
||||
checksum: 7f97068d7f9a155e088d3575b1f9922e68fa3015aae0c92625238d44b4e6c275bec2a281907702dedb402fca29a6cd4690499e916cb334d7c24c98099bc3d8b0
|
||||
checksum: 4a07d120ef8adf6c8e58a48abd02e075ffa215287cc6c3ef843d4fb08aeb0a566fe810ec9bfc376254468a2aa4f29bae154a60804a83af78dfa86d0e8e995476
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
Reference in New Issue
Block a user