mirror of
https://github.com/ajnart/homarr.git
synced 2025-11-12 00:15:48 +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
|
- 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
|
||||||
|
|||||||
7
.github/workflows/docker_dev.yml
vendored
7
.github/workflows/docker_dev.yml
vendored
@@ -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
1
.gitignore
vendored
@@ -32,6 +32,7 @@ yarn-error.log*
|
|||||||
|
|
||||||
# vercel
|
# vercel
|
||||||
.vercel
|
.vercel
|
||||||
|
.turbo
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
|
|
||||||
# storybook
|
# storybook
|
||||||
|
|||||||
@@ -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"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
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 {
|
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>
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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'}
|
||||||
</Badge>
|
variant="dot"
|
||||||
) : (
|
>
|
||||||
<Badge className={classes.textTransformUnset} color="red" variant="dot">
|
{secretIsPresent
|
||||||
{t('integration.type.undefined')}
|
? t('integration.type.defined')
|
||||||
</Badge>
|
: 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}
|
||||||
|
|||||||
@@ -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')}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ export const AvailableElementTypes = ({
|
|||||||
},
|
},
|
||||||
network: {
|
network: {
|
||||||
enabledStatusChecker: true,
|
enabledStatusChecker: true,
|
||||||
okStatus: [200],
|
statusCodes: ['200'],
|
||||||
},
|
},
|
||||||
behaviour: {
|
behaviour: {
|
||||||
isOpeningNewTab: true,
|
isOpeningNewTab: true,
|
||||||
|
|||||||
@@ -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';
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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 = () =>
|
||||||
queryKey: ['network-speed'],
|
useQuery({
|
||||||
queryFn: async (): Promise<NormalizedDownloadQueueResponse> => {
|
queryKey: ['network-speed'],
|
||||||
const response = await fetch('/api/modules/downloads');
|
queryFn: async (): Promise<NormalizedDownloadQueueResponse> => {
|
||||||
return response.json();
|
const response = await fetch('/api/modules/downloads');
|
||||||
},
|
return response.json();
|
||||||
refetchInterval: 3000,
|
},
|
||||||
});
|
refetchInterval: 3000,
|
||||||
|
});
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ async function Get(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const IntegrationTypeEndpointMap = new Map<AppIntegrationType['type'], string>([
|
const IntegrationTypeEndpointMap = new Map<AppIntegrationType['type'], string>([
|
||||||
['sonarr', useSonarrv4 ? '/api/v3/calendar' : '/api/calendar'],
|
['sonarr', useSonarrv4 ? '/api/v3/calendar' : '/api/calendar'],
|
||||||
['radarr', '/api/v3/calendar'],
|
['radarr', '/api/v3/calendar'],
|
||||||
['lidarr', '/api/v1/calendar'],
|
['lidarr', '/api/v1/calendar'],
|
||||||
['readarr', '/api/v1/calendar'],
|
['readarr', '/api/v1/calendar'],
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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'];
|
||||||
|
|||||||
@@ -14,33 +14,39 @@ 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;
|
| {
|
||||||
videoFrameRate: string | undefined;
|
videoCodec: string | undefined;
|
||||||
height: number | undefined;
|
videoFrameRate: string | undefined;
|
||||||
width: number | undefined;
|
height: number | undefined;
|
||||||
bitrate: number | undefined;
|
width: number | undefined;
|
||||||
} | undefined;
|
bitrate: number | undefined;
|
||||||
audio: {
|
}
|
||||||
audioCodec: string | undefined;
|
| undefined;
|
||||||
audioChannels: number | undefined;
|
audio:
|
||||||
} | undefined;
|
| {
|
||||||
transcoding: {
|
audioCodec: string | undefined;
|
||||||
context: string | undefined;
|
audioChannels: number | undefined;
|
||||||
sourceVideoCodec: string | undefined;
|
}
|
||||||
sourceAudioCodec: string | undefined;
|
| undefined;
|
||||||
videoDecision: string | undefined;
|
transcoding:
|
||||||
audioDecision: string | undefined;
|
| {
|
||||||
container: string | undefined;
|
context: string | undefined;
|
||||||
videoCodec: string | undefined;
|
sourceVideoCodec: string | undefined;
|
||||||
audioCodec: string | undefined;
|
sourceAudioCodec: string | undefined;
|
||||||
error: boolean | undefined;
|
videoDecision: string | undefined;
|
||||||
duration: number | undefined;
|
audioDecision: string | undefined;
|
||||||
audioChannels: number | undefined;
|
container: string | undefined;
|
||||||
width: number | undefined;
|
videoCodec: string | undefined;
|
||||||
height: number | undefined;
|
audioCodec: string | undefined;
|
||||||
transcodeHwRequested: boolean | undefined;
|
error: boolean | undefined;
|
||||||
timeStamp: number | undefined;
|
duration: number | undefined;
|
||||||
} | undefined;
|
audioChannels: number | undefined;
|
||||||
|
width: number | undefined;
|
||||||
|
height: number | undefined;
|
||||||
|
transcodeHwRequested: boolean | undefined;
|
||||||
|
timeStamp: number | undefined;
|
||||||
|
}
|
||||||
|
| undefined;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ interface AppBehaviourType {
|
|||||||
|
|
||||||
interface AppNetworkType {
|
interface AppNetworkType {
|
||||||
enabledStatusChecker: boolean;
|
enabledStatusChecker: boolean;
|
||||||
okStatus: number[];
|
statusCodes: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AppAppearanceType {
|
interface AppAppearanceType {
|
||||||
|
|||||||
@@ -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 (
|
||||||
<HomarrCardWrapper className={className}>
|
<ErrorBoundary>
|
||||||
<WidgetsMenu integration={widgetId} widget={widgetWithDefaultProps} />
|
<HomarrCardWrapper className={className}>
|
||||||
<WidgetComponent widget={widgetWithDefaultProps} />
|
<WidgetsMenu integration={widgetId} widget={widgetWithDefaultProps} />
|
||||||
</HomarrCardWrapper>
|
<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,
|
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;
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ function IFrameTile({ widget }: IFrameTileProps) {
|
|||||||
<IconUnlink size={36} strokeWidth={1.2} />
|
<IconUnlink size={36} strokeWidth={1.2} />
|
||||||
<Stack align="center" spacing={0}>
|
<Stack align="center" spacing={0}>
|
||||||
<Title order={6} align="center">
|
<Title order={6} align="center">
|
||||||
{t('card.errors.noUrl.title')}
|
{t('card.errors.noUrl.title')}
|
||||||
</Title>
|
</Title>
|
||||||
<Text align="center" maw={200}>
|
<Text align="center" maw={200}>
|
||||||
{t('card.errors.noUrl.text')}
|
{t('card.errors.noUrl.text')}
|
||||||
|
|||||||
@@ -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}>
|
||||||
|
|||||||
@@ -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,16 +135,18 @@ function RssTile({ widget }: RssTileProps) {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<Flex gap="xs">
|
<Flex gap="xs">
|
||||||
<MediaQuery query="(max-width: 1200px)" styles={{ display: 'none' }}>
|
{item.enclosure && (
|
||||||
<Image
|
<MediaQuery query="(max-width: 1200px)" styles={{ display: 'none' }}>
|
||||||
src={item.enclosure?.url ?? undefined}
|
<Image
|
||||||
width={140}
|
src={item.enclosure?.url ?? undefined}
|
||||||
height={140}
|
width={140}
|
||||||
radius="md"
|
height={140}
|
||||||
withPlaceholder
|
radius="md"
|
||||||
/>
|
withPlaceholder
|
||||||
</MediaQuery>
|
/>
|
||||||
<Flex gap={2} direction="column">
|
</MediaQuery>
|
||||||
|
)}
|
||||||
|
<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>
|
||||||
<Group>
|
{data.feed.lastBuildDate && (
|
||||||
<IconBulldozer size={14} />
|
<Group>
|
||||||
<Text color="dimmed" size="sm">
|
<IconBulldozer size={14} />
|
||||||
{data.feed.lastBuildDate}
|
<Text color="dimmed" size="sm">
|
||||||
</Text>
|
{data.feed.lastBuildDate}
|
||||||
</Group>
|
</Text>
|
||||||
|
</Group>
|
||||||
|
)}
|
||||||
{data.feed.feedUrl && (
|
{data.feed.feedUrl && (
|
||||||
<Group spacing="sm">
|
<Group spacing="sm">
|
||||||
<IconSpeakerphone size={14} />
|
<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 {
|
interface UseNetTileProps {
|
||||||
widget: IUsenetWidget;
|
widget: IUsenetWidget;
|
||||||
|
|||||||
@@ -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
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
|
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
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user