Merge pull request #535 from ajnart/new-update-indicator

New update indicator
This commit is contained in:
Thomas Camlong
2022-12-11 14:42:08 +09:00
committed by GitHub
10 changed files with 108 additions and 160 deletions

View File

@@ -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",

View File

@@ -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"
},

View File

@@ -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}

View File

@@ -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>
);
}

View File

@@ -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}
/>
);
}

View File

@@ -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 />

View File

@@ -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}

View File

@@ -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) {

View File

@@ -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' },

View File

@@ -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"