Update version check logic.
parent
57bb1cb655
commit
37cf2e3017
|
@ -1,21 +1,27 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import semver from 'semver';
|
|
||||||
import useVersion from 'hooks/useVersion';
|
import useVersion from 'hooks/useVersion';
|
||||||
import styles from './UpdateNotice.module.css';
|
import styles from './UpdateNotice.module.css';
|
||||||
import ButtonLayout from '../layout/ButtonLayout';
|
import ButtonLayout from '../layout/ButtonLayout';
|
||||||
import Button from './Button';
|
import Button from './Button';
|
||||||
|
import useForceUpdate from '../../hooks/useForceUpdate';
|
||||||
|
|
||||||
export default function UpdateNotice() {
|
export default function UpdateNotice() {
|
||||||
const versions = useVersion();
|
const forceUpdte = useForceUpdate();
|
||||||
|
const { hasUpdate, latest, updateCheck } = useVersion();
|
||||||
|
|
||||||
if (!versions) {
|
function handleViewClick() {
|
||||||
return null;
|
location.href = 'https://github.com/mikecao/umami/releases';
|
||||||
|
updateCheck();
|
||||||
|
forceUpdte();
|
||||||
}
|
}
|
||||||
|
|
||||||
const { current, latest } = versions;
|
function handleDismissClick() {
|
||||||
|
updateCheck();
|
||||||
|
forceUpdte();
|
||||||
|
}
|
||||||
|
|
||||||
if (latest && semver.gte(current, latest)) {
|
if (!hasUpdate) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,10 +35,10 @@ export default function UpdateNotice() {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ButtonLayout>
|
<ButtonLayout>
|
||||||
<Button size="xsmall" variant="action">
|
<Button size="xsmall" variant="action" onClick={handleViewClick}>
|
||||||
<FormattedMessage id="button.view-details" defaultMessage="View details" />
|
<FormattedMessage id="button.view-details" defaultMessage="View details" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button size="xsmall">
|
<Button size="xsmall" onClick={handleDismissClick}>
|
||||||
<FormattedMessage id="button.dismiss" defaultMessage="Dismiss" />
|
<FormattedMessage id="button.dismiss" defaultMessage="Dismiss" />
|
||||||
</Button>
|
</Button>
|
||||||
</ButtonLayout>
|
</ButtonLayout>
|
||||||
|
|
|
@ -26,7 +26,7 @@ export default function Settings() {
|
||||||
{
|
{
|
||||||
label: <FormattedMessage id="label.accounts" defaultMessage="Accounts" />,
|
label: <FormattedMessage id="label.accounts" defaultMessage="Accounts" />,
|
||||||
value: ACCOUNTS,
|
value: ACCOUNTS,
|
||||||
hidden: !user.is_admin,
|
hidden: !user?.is_admin,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: <FormattedMessage id="label.profile" defaultMessage="Profile" />,
|
label: <FormattedMessage id="label.profile" defaultMessage="Profile" />,
|
||||||
|
|
|
@ -1,20 +1,27 @@
|
||||||
import { useEffect } from 'react';
|
import { useEffect, useCallback } from 'react';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { getItem } from 'lib/web';
|
import semver from 'semver';
|
||||||
|
import { getItem, setItem } from 'lib/web';
|
||||||
import { checkVersion } from 'redux/actions/app';
|
import { checkVersion } from 'redux/actions/app';
|
||||||
|
import { VERSION_CHECK } from 'lib/constants';
|
||||||
const CHECK_INTERVAL = 24 * 60 * 60 * 1000;
|
|
||||||
|
|
||||||
export default function useVersion() {
|
export default function useVersion() {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const versions = useSelector(state => state.app.versions);
|
const versions = useSelector(state => state.app.versions);
|
||||||
|
const lastCheck = getItem(VERSION_CHECK);
|
||||||
|
|
||||||
|
const { current, latest } = versions;
|
||||||
|
const hasUpdate = latest && semver.gt(latest, current) && lastCheck?.version !== latest;
|
||||||
|
|
||||||
|
const updateCheck = useCallback(() => {
|
||||||
|
setItem(VERSION_CHECK, { version: latest, time: Date.now() });
|
||||||
|
}, [versions]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const lastCheck = getItem('umami.version-check');
|
if (!versions.latest) {
|
||||||
if (!lastCheck || Date.now() - lastCheck > CHECK_INTERVAL) {
|
|
||||||
dispatch(checkVersion());
|
dispatch(checkVersion());
|
||||||
}
|
}
|
||||||
}, []);
|
}, [versions]);
|
||||||
|
|
||||||
return versions;
|
return { ...versions, hasUpdate, updateCheck };
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,7 +55,7 @@
|
||||||
"message.get-tracking-code": "Get tracking code",
|
"message.get-tracking-code": "Get tracking code",
|
||||||
"message.go-to-settings": "Go to settings",
|
"message.go-to-settings": "Go to settings",
|
||||||
"message.incorrect-username-password": "Incorrect username/password.",
|
"message.incorrect-username-password": "Incorrect username/password.",
|
||||||
"message.new-version-available": "Version {latest} available! Current version: {current}",
|
"message.new-version-available": "A new version of umami {version} is available!",
|
||||||
"message.no-data-available": "No data available.",
|
"message.no-data-available": "No data available.",
|
||||||
"message.no-websites-configured": "You don't have any websites configured.",
|
"message.no-websites-configured": "You don't have any websites configured.",
|
||||||
"message.page-not-found": "Page not found.",
|
"message.page-not-found": "Page not found.",
|
||||||
|
|
|
@ -3,6 +3,7 @@ export const LOCALE_CONFIG = 'umami.locale';
|
||||||
export const TIMEZONE_CONFIG = 'umami.timezone';
|
export const TIMEZONE_CONFIG = 'umami.timezone';
|
||||||
export const DATE_RANGE_CONFIG = 'umami.date-range';
|
export const DATE_RANGE_CONFIG = 'umami.date-range';
|
||||||
export const THEME_CONFIG = 'umami.theme';
|
export const THEME_CONFIG = 'umami.theme';
|
||||||
|
export const VERSION_CHECK = 'umami.version-check';
|
||||||
|
|
||||||
export const THEME_COLORS = {
|
export const THEME_COLORS = {
|
||||||
light: {
|
light: {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { createSlice } from '@reduxjs/toolkit';
|
import { createSlice } from '@reduxjs/toolkit';
|
||||||
import { get, getItem } from 'lib/web';
|
import { getItem } from 'lib/web';
|
||||||
import { LOCALE_CONFIG, THEME_CONFIG } from 'lib/constants';
|
import { LOCALE_CONFIG, THEME_CONFIG } from 'lib/constants';
|
||||||
|
|
||||||
const app = createSlice({
|
const app = createSlice({
|
||||||
|
@ -40,30 +40,32 @@ export function checkVersion() {
|
||||||
},
|
},
|
||||||
} = getState();
|
} = getState();
|
||||||
|
|
||||||
const data = await get('https://api.github.com/repos/mikecao/umami/releases/latest');
|
const data = await fetch('https://api.github.com/repos/mikecao/umami/releases/latest', {
|
||||||
|
method: 'get',
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/vnd.github.v3+json',
|
||||||
|
},
|
||||||
|
}).then(res => {
|
||||||
|
if (res.ok) {
|
||||||
|
return res.json();
|
||||||
|
}
|
||||||
|
|
||||||
if (!data || !data['tag_name']) {
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const latest = data['tag_name'].startsWith('v') ? data['tag_name'].slice(1) : data['tag_name'];
|
const { tag_name } = data;
|
||||||
|
|
||||||
if (latest === current) {
|
const latest = tag_name.startsWith('v') ? tag_name.slice(1) : tag_name;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const latestArray = latest.split('.');
|
|
||||||
const currentArray = current.split('.');
|
|
||||||
|
|
||||||
for (let i = 0; i < 3; i++) {
|
|
||||||
if (Number(latestArray[i]) > Number(currentArray[i])) {
|
|
||||||
return dispatch(
|
return dispatch(
|
||||||
setVersions({
|
setVersions({
|
||||||
current,
|
current,
|
||||||
latest: latest,
|
latest,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue