+ client: handle update

This commit is contained in:
Ildar Kamalov 2019-05-16 17:24:30 +03:00 committed by Simon Zolin
parent 068072bc5a
commit cb3f7f2834
8 changed files with 155 additions and 18 deletions

View File

@ -260,5 +260,7 @@
"dns_addresses": "DNS addresses",
"down": "Down",
"fix": "Fix",
"dns_providers": "Here is a <0>list of known DNS providers</0> to choose from."
"dns_providers": "Here is a <0>list of known DNS providers</0> to choose from.",
"update_now": "Update now",
"processing_update": "Please wait, AdGuard Home is being updated"
}

View File

@ -4,7 +4,7 @@ import { t } from 'i18next';
import { showLoading, hideLoading } from 'react-redux-loading-bar';
import { normalizeHistory, normalizeFilteringStatus, normalizeLogs, normalizeTextarea } from '../helpers/helpers';
import { SETTINGS_NAMES } from '../helpers/constants';
import { SETTINGS_NAMES, CHECK_TIMEOUT } from '../helpers/constants';
import Api from '../api/Api';
const apiClient = new Api();
@ -154,6 +154,29 @@ export const getVersion = () => async (dispatch) => {
}
};
export const getUpdateRequest = createAction('GET_UPDATE_REQUEST');
export const getUpdateFailure = createAction('GET_UPDATE_FAILURE');
export const getUpdateSuccess = createAction('GET_UPDATE_SUCCESS');
export const getUpdate = () => async (dispatch) => {
dispatch(getUpdateRequest());
try {
await apiClient.getUpdate();
const timer = setInterval(async () => {
const dnsStatus = await apiClient.getGlobalStatus();
if (dnsStatus) {
clearInterval(timer);
dispatch(getUpdateSuccess());
window.location.reload(true);
}
}, CHECK_TIMEOUT);
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(getUpdateFailure());
}
};
export const getClientsRequest = createAction('GET_CLIENTS_REQUEST');
export const getClientsFailure = createAction('GET_CLIENTS_FAILURE');
export const getClientsSuccess = createAction('GET_CLIENTS_SUCCESS');

View File

@ -40,6 +40,8 @@ export default class Api {
GLOBAL_ENABLE_PROTECTION = { path: 'enable_protection', method: 'POST' };
GLOBAL_DISABLE_PROTECTION = { path: 'disable_protection', method: 'POST' };
GLOBAL_CLIENTS = { path: 'clients', method: 'GET' }
GLOBAL_CLIENTS = { path: 'clients', method: 'GET' };
GLOBAL_UPDATE = { path: 'update', method: 'POST' };
restartGlobalFiltering() {
const { path, method } = this.GLOBAL_RESTART;
@ -145,6 +147,11 @@ export default class Api {
return this.makeRequest(path, method);
}
getUpdate() {
const { path, method } = this.GLOBAL_UPDATE;
return this.makeRequest(path, method);
}
// Filtering
FILTERING_STATUS = { path: 'filtering/status', method: 'GET' };
FILTERING_ENABLE = { path: 'filtering/enable', method: 'POST' };

View File

@ -19,6 +19,7 @@ import Toasts from '../Toasts';
import Footer from '../ui/Footer';
import Status from '../ui/Status';
import UpdateTopline from '../ui/UpdateTopline';
import UpdateOverlay from '../ui/UpdateOverlay';
import EncryptionTopline from '../ui/EncryptionTopline';
import i18n from '../../i18n';
@ -37,6 +38,10 @@ class App extends Component {
this.props.enableDns();
};
handleUpdate = () => {
this.props.getUpdate();
}
setLanguage = () => {
const { processing, language } = this.props.dashboard;
@ -62,10 +67,16 @@ class App extends Component {
<HashRouter hashType='noslash'>
<Fragment>
{updateAvailable &&
<UpdateTopline
url={dashboard.announcementUrl}
version={dashboard.version}
/>
<Fragment>
<UpdateTopline
url={dashboard.announcementUrl}
version={dashboard.newVersion}
canAutoUpdate={dashboard.canAutoUpdate}
getUpdate={this.handleUpdate}
processingUpdate={dashboard.processingUpdate}
/>
<UpdateOverlay processingUpdate={dashboard.processingUpdate} />
</Fragment>
}
{!encryption.processing &&
<EncryptionTopline notAfter={encryption.not_after} />
@ -100,6 +111,7 @@ class App extends Component {
App.propTypes = {
getDnsStatus: PropTypes.func,
getUpdate: PropTypes.func,
enableDns: PropTypes.func,
dashboard: PropTypes.object,
isCoreRunning: PropTypes.bool,

View File

@ -0,0 +1,40 @@
.overlay {
display: none;
position: fixed;
top: 0;
left: 0;
z-index: 110;
width: 100%;
height: 100%;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 20px;
font-size: 28px;
font-weight: 600;
text-align: center;
background-color: rgba(255, 255, 255, 0.8);
}
.overlay--visible {
display: flex;
}
.overlay__loading {
width: 40px;
height: 40px;
margin-bottom: 20px;
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2047.6%2047.6%22%20height%3D%22100%25%22%20width%3D%22100%25%22%3E%3Cpath%20opacity%3D%22.235%22%20fill%3D%22%23979797%22%20d%3D%22M44.4%2011.9l-5.2%203c1.5%202.6%202.4%205.6%202.4%208.9%200%209.8-8%2017.8-17.8%2017.8-6.6%200-12.3-3.6-15.4-8.9l-5.2%203C7.3%2042.8%2015%2047.6%2023.8%2047.6c13.1%200%2023.8-10.7%2023.8-23.8%200-4.3-1.2-8.4-3.2-11.9z%22%2F%3E%3Cpath%20fill%3D%22%2366b574%22%20d%3D%22M3.2%2035.7C0%2030.2-.8%2023.8.8%2017.6%202.5%2011.5%206.4%206.4%2011.9%203.2%2017.4%200%2023.8-.8%2030%20.8c6.1%201.6%2011.3%205.6%2014.4%2011.1l-5.2%203c-2.4-4.1-6.2-7.1-10.8-8.3C23.8%205.4%2019%206%2014.9%208.4s-7.1%206.2-8.3%2010.8c-1.2%204.6-.6%209.4%201.8%2013.5l-5.2%203z%22%2F%3E%3C%2Fsvg%3E");
will-change: transform;
animation: clockwise 2s linear infinite;
}
@keyframes clockwise {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}

View File

@ -0,0 +1,26 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Trans, withNamespaces } from 'react-i18next';
import classnames from 'classnames';
import './Overlay.css';
const UpdateOverlay = (props) => {
const overlayClass = classnames({
overlay: true,
'overlay--visible': props.processingUpdate,
});
return (
<div className={overlayClass}>
<div className="overlay__loading"></div>
<Trans>processing_update</Trans>
</div>
);
};
UpdateOverlay.propTypes = {
processingUpdate: PropTypes.bool,
};
export default withNamespaces()(UpdateOverlay);

View File

@ -1,4 +1,4 @@
import React from 'react';
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { Trans, withNamespaces } from 'react-i18next';
@ -6,22 +6,37 @@ import Topline from './Topline';
const UpdateTopline = props => (
<Topline type="info">
<Trans
values={{ version: props.version }}
components={[
<a href={props.url} target="_blank" rel="noopener noreferrer" key="0">
Click here
</a>,
]}
>
update_announcement
</Trans>
<Fragment>
<Trans
values={{ version: props.version }}
components={[
<a href={props.url} target="_blank" rel="noopener noreferrer" key="0">
Click here
</a>,
]}
>
update_announcement
</Trans>
{props.canAutoUpdate &&
<button
type="button"
className="btn btn-sm btn-primary ml-3"
onClick={props.getUpdate}
disabled={props.processingUpdate}
>
<Trans>update_now</Trans>
</button>
}
</Fragment>
</Topline>
);
UpdateTopline.propTypes = {
version: PropTypes.string.isRequired,
version: PropTypes.string,
url: PropTypes.string.isRequired,
canAutoUpdate: PropTypes.bool,
getUpdate: PropTypes.func,
processingUpdate: PropTypes.bool,
};
export default withNamespaces()(UpdateTopline);

View File

@ -126,12 +126,16 @@ const dashboard = handleActions({
const {
version,
announcement_url: announcementUrl,
new_version: newVersion,
can_autoupdate: canAutoUpdate,
} = payload;
const newState = {
...state,
version,
announcementUrl,
newVersion,
canAutoUpdate,
isUpdateAvailable: true,
};
return newState;
@ -140,6 +144,13 @@ const dashboard = handleActions({
return state;
},
[actions.getUpdateRequest]: state => ({ ...state, processingUpdate: true }),
[actions.getUpdateFailure]: state => ({ ...state, processingUpdate: false }),
[actions.getUpdateSuccess]: (state) => {
const newState = { ...state, processingUpdate: false };
return newState;
},
[actions.getFilteringRequest]: state => ({ ...state, processingFiltering: true }),
[actions.getFilteringFailure]: state => ({ ...state, processingFiltering: false }),
[actions.getFilteringSuccess]: (state, { payload }) => {
@ -187,6 +198,7 @@ const dashboard = handleActions({
processingVersion: true,
processingFiltering: true,
processingClients: true,
processingUpdate: false,
upstreamDns: '',
bootstrapDns: '',
allServers: false,