diff --git a/client/package-lock.json b/client/package-lock.json index efac20b7..7c27b39b 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -14978,6 +14978,11 @@ "setimmediate": "^1.0.4" } }, + "tiny-version-compare": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/tiny-version-compare/-/tiny-version-compare-0.9.1.tgz", + "integrity": "sha512-kYim94l7ptSmj9rqxUMkrcMCJ448CS+hwqjA7OFcRi0ISdi0zjgdSUklQ4velVVECCjCo5frU3tNZ3oSgIKzsA==" + }, "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", diff --git a/client/package.json b/client/package.json index 183148a2..1e160b99 100644 --- a/client/package.json +++ b/client/package.json @@ -31,6 +31,7 @@ "redux-thunk": "^2.3.0", "svg-url-loader": "^2.3.2", "tabler-react": "^1.10.0", + "tiny-version-compare": "^0.9.1", "whatwg-fetch": "2.0.3" }, "devDependencies": { diff --git a/client/src/actions/index.js b/client/src/actions/index.js index 048fe77e..c4700198 100644 --- a/client/src/actions/index.js +++ b/client/src/actions/index.js @@ -164,6 +164,21 @@ export const getStats = () => async (dispatch) => { } }; +export const getVersionRequest = createAction('GET_VERSION_REQUEST'); +export const getVersionFailure = createAction('GET_VERSION_FAILURE'); +export const getVersionSuccess = createAction('GET_VERSION_SUCCESS'); + +export const getVersion = () => async (dispatch) => { + dispatch(getVersionRequest()); + try { + const newVersion = await apiClient.getGlobalVersion(); + dispatch(getVersionSuccess(newVersion)); + } catch (error) { + dispatch(addErrorToast({ error })); + dispatch(getVersionFailure()); + } +}; + export const getTopStatsRequest = createAction('GET_TOP_STATS_REQUEST'); export const getTopStatsFailure = createAction('GET_TOP_STATS_FAILURE'); export const getTopStatsSuccess = createAction('GET_TOP_STATS_SUCCESS'); diff --git a/client/src/api/Api.js b/client/src/api/Api.js index 8cec8a05..4408eb51 100644 --- a/client/src/api/Api.js +++ b/client/src/api/Api.js @@ -31,6 +31,7 @@ export default class Api { GLOBAL_QUERY_LOG_ENABLE = { path: 'querylog_enable', method: 'POST' }; GLOBAL_QUERY_LOG_DISABLE = { path: 'querylog_disable', method: 'POST' }; GLOBAL_SET_UPSTREAM_DNS = { path: 'set_upstream_dns', method: 'POST' }; + GLOBAL_VERSION = { path: 'version.json', method: 'GET' }; restartGlobalFiltering() { const { path, method } = this.GLOBAL_RESTART; @@ -107,6 +108,11 @@ export default class Api { return this.makeRequest(path, method, config); } + getGlobalVersion() { + const { path, method } = this.GLOBAL_VERSION; + return this.makeRequest(path, method); + } + // Filtering FILTERING_STATUS = { path: 'filtering/status', method: 'GET' }; FILTERING_ENABLE = { path: 'filtering/enable', method: 'POST' }; diff --git a/client/src/components/App/index.js b/client/src/components/App/index.js index 2716881f..b4b040a9 100644 --- a/client/src/components/App/index.js +++ b/client/src/components/App/index.js @@ -15,12 +15,13 @@ import Filters from '../../containers/Filters'; import Logs from '../../containers/Logs'; import Footer from '../ui/Footer'; import Toasts from '../Toasts'; - import Status from '../ui/Status'; +import Update from '../ui/Update'; class App extends Component { componentDidMount() { this.props.getDnsStatus(); + this.props.getVersion(); } handleStatusChange = () => { @@ -29,9 +30,20 @@ class App extends Component { render() { const { dashboard } = this.props; + const updateAvailable = + !dashboard.processingVersions && + dashboard.isCoreRunning && + dashboard.isUpdateAvailable; + return ( + {updateAvailable && + + }
@@ -65,6 +77,7 @@ App.propTypes = { dashboard: PropTypes.object, isCoreRunning: PropTypes.bool, error: PropTypes.string, + getVersion: PropTypes.func, }; export default App; diff --git a/client/src/components/Header/Header.css b/client/src/components/Header/Header.css index fae34451..d3a86a61 100644 --- a/client/src/components/Header/Header.css +++ b/client/src/components/Header/Header.css @@ -67,11 +67,15 @@ } .nav-version { - padding: 16px 0; - font-size: 0.85rem; + padding: 7px 0; + font-size: 0.80rem; text-align: right; } +.nav-version__value { + font-weight: 600; +} + .header-brand-img { height: 26px; } @@ -103,7 +107,7 @@ .nav-version { padding: 0; - font-size: 0.9rem; + font-size: 0.85rem; } } diff --git a/client/src/components/Header/Version.js b/client/src/components/Header/Version.js index bbf3f1f3..981acf8d 100644 --- a/client/src/components/Header/Version.js +++ b/client/src/components/Header/Version.js @@ -5,7 +5,12 @@ export default function Version(props) { const { dnsVersion, dnsAddress, dnsPort } = props; return (
- version {dnsVersion} / address: {dnsAddress}:{dnsPort} +
+ version: {dnsVersion} +
+
+ address: {dnsAddress}:{dnsPort} +
); } diff --git a/client/src/components/ui/Update.css b/client/src/components/ui/Update.css new file mode 100644 index 00000000..ec7ec532 --- /dev/null +++ b/client/src/components/ui/Update.css @@ -0,0 +1,6 @@ +.update { + position: relative; + z-index: 102; + margin-bottom: 0; + padding: 0.75rem 0; +} diff --git a/client/src/components/ui/Update.js b/client/src/components/ui/Update.js new file mode 100644 index 00000000..5df9df65 --- /dev/null +++ b/client/src/components/ui/Update.js @@ -0,0 +1,19 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import './Update.css'; + +const Update = props => ( +
+
+ {props.announcement} Click here for more info. +
+
+); + +Update.propTypes = { + announcement: PropTypes.string.isRequired, + announcementUrl: PropTypes.string.isRequired, +}; + +export default Update; diff --git a/client/src/reducers/index.js b/client/src/reducers/index.js index d141ecc3..86c4d49a 100644 --- a/client/src/reducers/index.js +++ b/client/src/reducers/index.js @@ -1,6 +1,7 @@ import { combineReducers } from 'redux'; import { handleActions } from 'redux-actions'; import { loadingBarReducer } from 'react-redux-loading-bar'; +import versionCompare from 'tiny-version-compare'; import nanoid from 'nanoid'; import * as actions from '../actions'; @@ -100,12 +101,36 @@ const dashboard = handleActions({ const { queryLogEnabled } = state; return ({ ...state, queryLogEnabled: !queryLogEnabled, logStatusProcessing: false }); }, + + [actions.getVersionRequest]: state => ({ ...state, processingVersion: true }), + [actions.getVersionFailure]: state => ({ ...state, processingVersion: false }), + [actions.getVersionSuccess]: (state, { payload }) => { + const currentVersion = state.dnsVersion === 'undefined' ? 0 : state.dnsVersion; + + if (versionCompare(currentVersion, payload.version) === -1) { + const { + announcement, + announcement_url: announcementUrl, + } = payload; + + const newState = { + ...state, + announcement, + announcementUrl, + isUpdateAvailable: true, + }; + return newState; + } + + return state; + }, }, { processing: true, isCoreRunning: false, processingTopStats: true, processingStats: true, logStatusProcessing: false, + processingVersion: true, }); const queryLogs = handleActions({