mirror of
https://github.com/ajnart/homarr.git
synced 2025-11-15 09:46:19 +01:00
Merge pull request #535 from ajnart/new-update-indicator
New update indicator
This commit is contained in:
@@ -61,6 +61,7 @@
|
||||
"next": "12.2.0",
|
||||
"next-i18next": "^11.3.0",
|
||||
"nzbget-api": "^0.0.3",
|
||||
"ping": "^0.4.2",
|
||||
"prism-react-renderer": "^1.3.5",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
@@ -75,6 +76,7 @@
|
||||
"@next/eslint-plugin-next": "^12.1.4",
|
||||
"@types/dockerode": "^3.3.9",
|
||||
"@types/node": "17.0.1",
|
||||
"@types/ping": "^0.4.1",
|
||||
"@types/react": "17.0.1",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@typescript-eslint/eslint-plugin": "^5.30.7",
|
||||
|
||||
@@ -113,12 +113,6 @@
|
||||
"advancedOptions": {
|
||||
"title": "Advanced options",
|
||||
"form": {
|
||||
"httpStatusCodes": {
|
||||
"label": "HTTP Status Codes",
|
||||
"placeholder": "Select valid status codes",
|
||||
"clearButtonLabel": "Clear selection",
|
||||
"nothingFound": "Nothing found"
|
||||
},
|
||||
"openServiceInNewTab": {
|
||||
"label": "Open service in new tab"
|
||||
},
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
Image,
|
||||
LoadingOverlay,
|
||||
Modal,
|
||||
MultiSelect,
|
||||
PasswordInput,
|
||||
Select,
|
||||
Space,
|
||||
@@ -25,7 +24,7 @@ import { useTranslation } from 'next-i18next';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { useConfig } from '../../tools/state';
|
||||
import { tryMatchPort, ServiceTypeList, StatusCodes, Config } from '../../tools/types';
|
||||
import { tryMatchPort, ServiceTypeList, Config } from '../../tools/types';
|
||||
import apiKeyPaths from './apiKeyPaths.json';
|
||||
import Tip from '../layout/Tip';
|
||||
|
||||
@@ -121,7 +120,6 @@ export function AddAppShelfItemForm(props: AddAppShelfItemFormProps) {
|
||||
password: props.password ?? undefined,
|
||||
openedUrl: props.openedUrl ?? undefined,
|
||||
ping: props.ping ?? true,
|
||||
status: props.status ?? ['200'],
|
||||
newTab: props.newTab ?? true,
|
||||
},
|
||||
validate: {
|
||||
@@ -139,12 +137,6 @@ export function AddAppShelfItemForm(props: AddAppShelfItemFormProps) {
|
||||
}
|
||||
return null;
|
||||
},
|
||||
status: (value: string[]) => {
|
||||
if (!value.length) {
|
||||
return t('modal.form.validation.noStatusCodeSelected');
|
||||
}
|
||||
return null;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -190,12 +182,6 @@ export function AddAppShelfItemForm(props: AddAppShelfItemFormProps) {
|
||||
if (newForm.openedUrl === '') newForm.openedUrl = undefined;
|
||||
if (newForm.category === null) newForm.category = undefined;
|
||||
if (newForm.ping === true) newForm.ping = undefined;
|
||||
if (
|
||||
(newForm.status.length === 1 && newForm.status[0] === '200') ||
|
||||
newForm.ping === false
|
||||
) {
|
||||
delete newForm.status;
|
||||
}
|
||||
// If service already exists, update it.
|
||||
if (config.services && config.services.find((s) => s.id === newForm.id)) {
|
||||
setConfig({
|
||||
@@ -451,26 +437,10 @@ export function AddAppShelfItemForm(props: AddAppShelfItemFormProps) {
|
||||
<Space h="sm" />
|
||||
<Stack>
|
||||
<Switch
|
||||
label={t('modal.tabs.advancedOptions.form.ping.label')}
|
||||
label="Ping service"
|
||||
defaultChecked={form.values.ping}
|
||||
{...form.getInputProps('ping')}
|
||||
/>
|
||||
{form.values.ping && (
|
||||
<MultiSelect
|
||||
required
|
||||
label={t('modal.tabs.advancedOptions.form.httpStatusCodes.label')}
|
||||
data={StatusCodes}
|
||||
placeholder={t('modal.tabs.advancedOptions.form.httpStatusCodes.placeholder')}
|
||||
clearButtonLabel={t(
|
||||
'modal.tabs.advancedOptions.form.httpStatusCodes.clearButtonLabel'
|
||||
)}
|
||||
nothingFound={t('modal.tabs.advancedOptions.form.httpStatusCodes.nothingFound')}
|
||||
defaultValue={['200']}
|
||||
clearable
|
||||
searchable
|
||||
{...form.getInputProps('status')}
|
||||
/>
|
||||
)}
|
||||
<Switch
|
||||
label={t('modal.tabs.advancedOptions.form.openServiceInNewTab.label')}
|
||||
defaultChecked={form.values.newTab}
|
||||
|
||||
@@ -1,15 +1,30 @@
|
||||
import { ActionIcon, Title, Tooltip, Drawer, Tabs, ScrollArea } from '@mantine/core';
|
||||
import { useHotkeys } from '@mantine/hooks';
|
||||
import { useState } from 'react';
|
||||
import { IconSettings } from '@tabler/icons';
|
||||
import {
|
||||
ActionIcon,
|
||||
Title,
|
||||
Tooltip,
|
||||
Drawer,
|
||||
Tabs,
|
||||
ScrollArea,
|
||||
Indicator,
|
||||
Alert,
|
||||
Notification,
|
||||
Anchor,
|
||||
} from '@mantine/core';
|
||||
import { useElementSize, useHotkeys, useViewportSize } from '@mantine/hooks';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { IconInfoCircle, IconSettings } from '@tabler/icons';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
import AdvancedSettings from './AdvancedSettings';
|
||||
import CommonSettings from './CommonSettings';
|
||||
import Credits from './Credits';
|
||||
import { CURRENT_VERSION, REPO_URL } from '../../../data/constants';
|
||||
import Link from 'next/link';
|
||||
import { NextLink } from '@mantine/next';
|
||||
|
||||
function SettingsMenu(props: any) {
|
||||
function SettingsMenu({ newVersionAvailable }: { newVersionAvailable: string }) {
|
||||
const { t } = useTranslation('settings/common');
|
||||
const { height, width } = useViewportSize();
|
||||
|
||||
return (
|
||||
<Tabs defaultValue="Common">
|
||||
@@ -18,13 +33,16 @@ function SettingsMenu(props: any) {
|
||||
<Tabs.Tab value="Customizations">{t('tabs.customizations')}</Tabs.Tab>
|
||||
</Tabs.List>
|
||||
<Tabs.Panel data-autofocus value="Common">
|
||||
<ScrollArea style={{ height: '78vh' }} offsetScrollbars>
|
||||
<ScrollArea style={{ height: height - 100 }} offsetScrollbars>
|
||||
{newVersionAvailable && <NewUpdateIndicator newVersionAvailable={newVersionAvailable} />}
|
||||
<CommonSettings />
|
||||
<Credits />
|
||||
</ScrollArea>
|
||||
</Tabs.Panel>
|
||||
<Tabs.Panel value="Customizations">
|
||||
<ScrollArea style={{ height: '78vh' }} offsetScrollbars>
|
||||
<ScrollArea style={{ height: height - 120 }} offsetScrollbars>
|
||||
<AdvancedSettings />
|
||||
<Credits />
|
||||
</ScrollArea>
|
||||
</Tabs.Panel>
|
||||
</Tabs>
|
||||
@@ -34,6 +52,17 @@ function SettingsMenu(props: any) {
|
||||
export function SettingsMenuButton(props: any) {
|
||||
useHotkeys([['ctrl+L', () => setOpened(!opened)]]);
|
||||
const { t } = useTranslation('settings/common');
|
||||
const [newVersionAvailable, setNewVersionAvailable] = useState<string>('');
|
||||
useEffect(() => {
|
||||
// Fetch Data here when component first mounted
|
||||
fetch(`https://api.github.com/repos/${REPO_URL}/releases/latest`).then((res) => {
|
||||
res.json().then((data) => {
|
||||
if (data.tag_name > CURRENT_VERSION) {
|
||||
setNewVersionAvailable(data.tag_name);
|
||||
}
|
||||
});
|
||||
});
|
||||
}, [CURRENT_VERSION]);
|
||||
|
||||
const [opened, setOpened] = useState(false);
|
||||
|
||||
@@ -47,21 +76,47 @@ export function SettingsMenuButton(props: any) {
|
||||
opened={props.opened || opened}
|
||||
onClose={() => setOpened(false)}
|
||||
>
|
||||
<SettingsMenu />
|
||||
<Credits />
|
||||
<SettingsMenu newVersionAvailable={newVersionAvailable} />
|
||||
</Drawer>
|
||||
<Tooltip label={t('tooltip')}>
|
||||
<ActionIcon
|
||||
variant="default"
|
||||
radius="md"
|
||||
size="xl"
|
||||
color="blue"
|
||||
style={props.style}
|
||||
onClick={() => setOpened(true)}
|
||||
>
|
||||
<IconSettings />
|
||||
</ActionIcon>
|
||||
<Indicator size={15} color="blue" withBorder processing disabled={!newVersionAvailable}>
|
||||
<ActionIcon
|
||||
variant="default"
|
||||
radius="md"
|
||||
size="xl"
|
||||
color="blue"
|
||||
style={props.style}
|
||||
onClick={() => setOpened(true)}
|
||||
>
|
||||
<IconSettings />
|
||||
</ActionIcon>
|
||||
</Indicator>
|
||||
</Tooltip>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function NewUpdateIndicator({ newVersionAvailable }: { newVersionAvailable: string }) {
|
||||
return (
|
||||
<Notification
|
||||
mt={10}
|
||||
icon={<IconInfoCircle size={25} />}
|
||||
disallowClose
|
||||
color="teal"
|
||||
radius="md"
|
||||
title="New update available"
|
||||
hidden={newVersionAvailable === ''}
|
||||
>
|
||||
Version{' '}
|
||||
<b>
|
||||
<Anchor
|
||||
target="_blank"
|
||||
href={`https://github.com/ajnart/homarr/releases/tag/${newVersionAvailable}`}
|
||||
>
|
||||
{newVersionAvailable}
|
||||
</Anchor>
|
||||
</b>{' '}
|
||||
is available ! Current version: {CURRENT_VERSION}
|
||||
</Notification>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { createStyles, Footer as FooterComponent } from '@mantine/core';
|
||||
import { showNotification } from '@mantine/notifications';
|
||||
import { IconAlertCircle as AlertCircle } from '@tabler/icons';
|
||||
import { CURRENT_VERSION, REPO_URL } from '../../../data/constants';
|
||||
|
||||
const useStyles = createStyles((theme) => ({
|
||||
footer: {
|
||||
borderTop: `1px solid ${
|
||||
theme.colorScheme === 'dark' ? theme.colors.dark[5] : theme.colors.gray[2]
|
||||
}`,
|
||||
},
|
||||
|
||||
inner: {
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
padding: `${theme.spacing.md}px ${theme.spacing.md}px`,
|
||||
|
||||
[theme.fn.smallerThan('sm')]: {
|
||||
flexDirection: 'column',
|
||||
},
|
||||
},
|
||||
|
||||
links: {
|
||||
[theme.fn.smallerThan('sm')]: {
|
||||
marginTop: theme.spacing.lg,
|
||||
marginBottom: theme.spacing.sm,
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
interface FooterCenteredProps {
|
||||
links: { link: string; label: string }[];
|
||||
}
|
||||
|
||||
export function Footer({ links }: FooterCenteredProps) {
|
||||
useEffect(() => {
|
||||
// Fetch Data here when component first mounted
|
||||
fetch(`https://api.github.com/repos/${REPO_URL}/releases/latest`).then((res) => {
|
||||
res.json().then((data) => {
|
||||
if (data.tag_name > CURRENT_VERSION) {
|
||||
showNotification({
|
||||
color: 'yellow',
|
||||
autoClose: false,
|
||||
title: 'New version available',
|
||||
icon: <AlertCircle />,
|
||||
message: `Version ${data.tag_name} is available, update now!`,
|
||||
});
|
||||
} else if (data.tag_name < CURRENT_VERSION) {
|
||||
showNotification({
|
||||
color: 'orange',
|
||||
autoClose: 5000,
|
||||
title: 'You are using a development version',
|
||||
icon: <AlertCircle />,
|
||||
message: 'This version of Homarr is still in development! Bugs are expected 🐛',
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<FooterComponent
|
||||
height="auto"
|
||||
style={{
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
clear: 'both',
|
||||
}}
|
||||
children={undefined}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import { AppShell, createStyles } from '@mantine/core';
|
||||
import { Header } from './header/Header';
|
||||
import { Footer } from './Footer';
|
||||
import Aside from './Aside';
|
||||
import Navbar from './Navbar';
|
||||
import { HeaderConfig } from './header/HeaderConfig';
|
||||
@@ -30,7 +29,6 @@ export default function Layout({ children, style }: any) {
|
||||
header={<Header />}
|
||||
navbar={widgetPosition ? <Navbar /> : undefined}
|
||||
aside={widgetPosition ? undefined : <Aside />}
|
||||
footer={<Footer links={[]} />}
|
||||
>
|
||||
<HeaderConfig />
|
||||
<Background />
|
||||
|
||||
@@ -62,7 +62,7 @@ export default function PingComponent(props: any) {
|
||||
<motion.div
|
||||
style={{ position: 'absolute', bottom: 20, right: 20 }}
|
||||
animate={{
|
||||
scale: isOnline === 'online' ? [1, 0.8, 1] : 1,
|
||||
scale: isOnline === 'online' ? [1, 0.7, 1] : 1,
|
||||
}}
|
||||
transition={{ repeat: Infinity, duration: 2.5, ease: 'easeInOut' }}
|
||||
>
|
||||
@@ -78,7 +78,7 @@ export default function PingComponent(props: any) {
|
||||
}
|
||||
>
|
||||
<Indicator
|
||||
size={13}
|
||||
size={15}
|
||||
color={isOnline === 'online' ? 'green' : isOnline === 'down' ? 'red' : 'yellow'}
|
||||
>
|
||||
{null}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import axios from 'axios';
|
||||
import https from 'https';
|
||||
import ping from 'ping';
|
||||
import { NextApiRequest, NextApiResponse } from 'next';
|
||||
|
||||
async function Get(req: NextApiRequest, res: NextApiResponse) {
|
||||
|
||||
@@ -35,28 +35,6 @@ interface ConfigModule {
|
||||
};
|
||||
}
|
||||
|
||||
export const StatusCodes = [
|
||||
{ value: '200', label: '200 - OK', group: 'Sucessful responses' },
|
||||
{ value: '204', label: '204 - No Content', group: 'Sucessful responses' },
|
||||
{ value: '301', label: '301 - Moved Permanently', group: 'Redirection responses' },
|
||||
{ value: '302', label: '302 - Found / Moved Temporarily', group: 'Redirection responses' },
|
||||
{ value: '304', label: '304 - Not Modified', group: 'Redirection responses' },
|
||||
{ value: '307', label: '307 - Temporary Redirect', group: 'Redirection responses' },
|
||||
{ value: '308', label: '308 - Permanent Redirect', group: 'Redirection responses' },
|
||||
{ value: '400', label: '400 - Bad Request', group: 'Client error responses' },
|
||||
{ value: '401', label: '401 - Unauthorized', group: 'Client error responses' },
|
||||
{ value: '403', label: '403 - Forbidden', group: 'Client error responses' },
|
||||
{ value: '404', label: '404 - Not Found', group: 'Client error responses' },
|
||||
{ value: '405', label: '405 - Method Not Allowed', group: 'Client error responses' },
|
||||
{ value: '408', label: '408 - Request Timeout', group: 'Client error responses' },
|
||||
{ value: '410', label: '410 - Gone', group: 'Client error responses' },
|
||||
{ value: '429', label: '429 - Too Many Requests', group: 'Client error responses' },
|
||||
{ value: '500', label: '500 - Internal Server Error', group: 'Server error responses' },
|
||||
{ value: '502', label: '502 - Bad Gateway', group: 'Server error responses' },
|
||||
{ value: '503', label: '503 - Service Unavailable', group: 'Server error responses' },
|
||||
{ value: '054', label: '504 - Gateway Timeout Error', group: 'Server error responses' },
|
||||
];
|
||||
|
||||
export const Targets = [
|
||||
{ value: '_blank', label: 'New Tab' },
|
||||
{ value: '_top', label: 'Same Window' },
|
||||
|
||||
28
yarn.lock
28
yarn.lock
@@ -2179,6 +2179,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/ping@npm:^0.4.1":
|
||||
version: 0.4.1
|
||||
resolution: "@types/ping@npm:0.4.1"
|
||||
checksum: 9b94837fe66df70558c5a42b0e0c8371b4950ab56b96c42c8df809ff2cf52477dd0a7e01d2e6b38af8bb6683b3dcb54587960b96b4b1f3d40fdb529aea348ad0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/prettier@npm:^2.1.5":
|
||||
version: 2.6.3
|
||||
resolution: "@types/prettier@npm:2.6.3"
|
||||
@@ -4843,6 +4850,7 @@ __metadata:
|
||||
"@tanstack/react-query": ^4.2.1
|
||||
"@types/dockerode": ^3.3.9
|
||||
"@types/node": 17.0.1
|
||||
"@types/ping": ^0.4.1
|
||||
"@types/react": 17.0.1
|
||||
"@types/uuid": ^8.3.4
|
||||
"@typescript-eslint/eslint-plugin": ^5.30.7
|
||||
@@ -4874,6 +4882,7 @@ __metadata:
|
||||
next: 12.2.0
|
||||
next-i18next: ^11.3.0
|
||||
nzbget-api: ^0.0.3
|
||||
ping: ^0.4.2
|
||||
prettier: ^2.7.1
|
||||
prism-react-renderer: ^1.3.5
|
||||
react: ^18.2.0
|
||||
@@ -6897,6 +6906,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ping@npm:^0.4.2":
|
||||
version: 0.4.2
|
||||
resolution: "ping@npm:0.4.2"
|
||||
dependencies:
|
||||
q: 1.x
|
||||
underscore: ^1.12.0
|
||||
checksum: 43992c76fb3294734248753f2028d9fab3b919dbfae79a5ea6df7e81fc2d6d555dd0b195d6c3dbc5c89aa9dba1cd8eb58d5ecedad103ecfee64df516e5f3665b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pirates@npm:^4.0.4":
|
||||
version: 4.0.5
|
||||
resolution: "pirates@npm:4.0.5"
|
||||
@@ -7064,7 +7083,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"q@npm:^1.4.1":
|
||||
"q@npm:1.x, q@npm:^1.4.1":
|
||||
version: 1.5.1
|
||||
resolution: "q@npm:1.5.1"
|
||||
checksum: 147baa93c805bc1200ed698bdf9c72e9e42c05f96d007e33a558b5fdfd63e5ea130e99313f28efc1783e90e6bdb4e48b67a36fcc026b7b09202437ae88a1fb12
|
||||
@@ -8177,6 +8196,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"underscore@npm:^1.12.0":
|
||||
version: 1.13.6
|
||||
resolution: "underscore@npm:1.13.6"
|
||||
checksum: d5cedd14a9d0d91dd38c1ce6169e4455bb931f0aaf354108e47bd46d3f2da7464d49b2171a5cf786d61963204a42d01ea1332a903b7342ad428deaafaf70ec36
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"unique-filename@npm:^1.1.1":
|
||||
version: 1.1.1
|
||||
resolution: "unique-filename@npm:1.1.1"
|
||||
|
||||
Reference in New Issue
Block a user