feat: show alert/prompt when settings changes require restart (#2401)
* fix: correct 'StatusChecker' typo * feat: add restart required check to StatusChecker * fix(perms): remove MANAGE_SETTINGS permission * fix: allow alert to be dismissed * fix(lang): add missing string in SettingsServices * fix(frontend): fix modal icon border * fix(frontend): un-dismiss alert if setting reverted not require server restart * fix(backend): restart flag only needs to track main settings * fix: rebase issue * refactor: appease Prettier * refactor: swap settings badge order * fix: type import for MainSettings * test: add cypress test for restart promptpull/2917/head
parent
70dc4c4b3b
commit
f3e56da3b7
@ -0,0 +1,32 @@
|
|||||||
|
describe('General Settings', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.login(Cypress.env('ADMIN_EMAIL'), Cypress.env('ADMIN_PASSWORD'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('opens the settings page from the home page', () => {
|
||||||
|
cy.visit('/');
|
||||||
|
|
||||||
|
cy.get('[data-testid=sidebar-toggle]').click();
|
||||||
|
cy.get('[data-testid=sidebar-menu-settings-mobile]').click();
|
||||||
|
|
||||||
|
cy.get('.heading').should('contain', 'General Settings');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('modifies setting that requires restart', () => {
|
||||||
|
cy.visit('/settings');
|
||||||
|
|
||||||
|
cy.get('#trustProxy').click();
|
||||||
|
cy.get('form').submit();
|
||||||
|
cy.get('[data-testid=modal-title]').should(
|
||||||
|
'contain',
|
||||||
|
'Server Restart Required'
|
||||||
|
);
|
||||||
|
|
||||||
|
cy.get('[data-testid=modal-ok-button]').click();
|
||||||
|
cy.get('[data-testid=modal-title]').should('not.exist');
|
||||||
|
|
||||||
|
cy.get('[type=checkbox]#trustProxy').click();
|
||||||
|
cy.get('form').submit();
|
||||||
|
cy.get('[data-testid=modal-title]').should('not.exist');
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,23 @@
|
|||||||
|
import type { MainSettings } from '../lib/settings';
|
||||||
|
import { getSettings } from '../lib/settings';
|
||||||
|
|
||||||
|
class RestartFlag {
|
||||||
|
private settings: MainSettings;
|
||||||
|
|
||||||
|
public initializeSettings(settings: MainSettings): void {
|
||||||
|
this.settings = { ...settings };
|
||||||
|
}
|
||||||
|
|
||||||
|
public isSet(): boolean {
|
||||||
|
const settings = getSettings().main;
|
||||||
|
|
||||||
|
return (
|
||||||
|
this.settings.csrfProtection !== settings.csrfProtection ||
|
||||||
|
this.settings.trustProxy !== settings.trustProxy
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const restartFlag = new RestartFlag();
|
||||||
|
|
||||||
|
export default restartFlag;
|
@ -1,54 +0,0 @@
|
|||||||
import { SparklesIcon } from '@heroicons/react/outline';
|
|
||||||
import React from 'react';
|
|
||||||
import { defineMessages, useIntl } from 'react-intl';
|
|
||||||
import useSWR from 'swr';
|
|
||||||
import type { StatusResponse } from '../../../server/interfaces/api/settingsInterfaces';
|
|
||||||
import Modal from '../Common/Modal';
|
|
||||||
import Transition from '../Transition';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
newversionavailable: 'Application Update',
|
|
||||||
newversionDescription:
|
|
||||||
'Overseerr has been updated! Please click the button below to reload the page.',
|
|
||||||
reloadOverseerr: 'Reload',
|
|
||||||
});
|
|
||||||
|
|
||||||
const StatusChecker: React.FC = () => {
|
|
||||||
const intl = useIntl();
|
|
||||||
const { data, error } = useSWR<StatusResponse>('/api/v1/status', {
|
|
||||||
refreshInterval: 60 * 1000,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!data && !error) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!data) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Transition
|
|
||||||
enter="opacity-0 transition duration-300"
|
|
||||||
enterFrom="opacity-0"
|
|
||||||
enterTo="opacity-100"
|
|
||||||
leave="opacity-100 transition duration-300"
|
|
||||||
leaveFrom="opacity-100"
|
|
||||||
leaveTo="opacity-0"
|
|
||||||
appear
|
|
||||||
show={data.commitTag !== process.env.commitTag}
|
|
||||||
>
|
|
||||||
<Modal
|
|
||||||
iconSvg={<SparklesIcon />}
|
|
||||||
title={intl.formatMessage(messages.newversionavailable)}
|
|
||||||
onOk={() => location.reload()}
|
|
||||||
okText={intl.formatMessage(messages.reloadOverseerr)}
|
|
||||||
backgroundClickable={false}
|
|
||||||
>
|
|
||||||
{intl.formatMessage(messages.newversionDescription)}
|
|
||||||
</Modal>
|
|
||||||
</Transition>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default StatusChecker;
|
|
@ -0,0 +1,94 @@
|
|||||||
|
import { RefreshIcon, SparklesIcon } from '@heroicons/react/outline';
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
|
import useSWR from 'swr';
|
||||||
|
import type { StatusResponse } from '../../../server/interfaces/api/settingsInterfaces';
|
||||||
|
import useSettings from '../../hooks/useSettings';
|
||||||
|
import { Permission, useUser } from '../../hooks/useUser';
|
||||||
|
import globalMessages from '../../i18n/globalMessages';
|
||||||
|
import Modal from '../Common/Modal';
|
||||||
|
import Transition from '../Transition';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
appUpdated: '{applicationTitle} Updated',
|
||||||
|
appUpdatedDescription:
|
||||||
|
'Please click the button below to reload the application.',
|
||||||
|
reloadApp: 'Reload {applicationTitle}',
|
||||||
|
restartRequired: 'Server Restart Required',
|
||||||
|
restartRequiredDescription:
|
||||||
|
'Please restart the server to apply the updated settings.',
|
||||||
|
});
|
||||||
|
|
||||||
|
const StatusChecker: React.FC = () => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const settings = useSettings();
|
||||||
|
const { hasPermission } = useUser();
|
||||||
|
const { data, error } = useSWR<StatusResponse>('/api/v1/status', {
|
||||||
|
refreshInterval: 60 * 1000,
|
||||||
|
});
|
||||||
|
const [alertDismissed, setAlertDismissed] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!data?.restartRequired) {
|
||||||
|
setAlertDismissed(false);
|
||||||
|
}
|
||||||
|
}, [data?.restartRequired]);
|
||||||
|
|
||||||
|
if (!data && !error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Transition
|
||||||
|
enter="opacity-0 transition duration-300"
|
||||||
|
enterFrom="opacity-0"
|
||||||
|
enterTo="opacity-100"
|
||||||
|
leave="opacity-100 transition duration-300"
|
||||||
|
leaveFrom="opacity-100"
|
||||||
|
leaveTo="opacity-0"
|
||||||
|
appear
|
||||||
|
show={
|
||||||
|
!alertDismissed &&
|
||||||
|
((hasPermission(Permission.ADMIN) && data.restartRequired) ||
|
||||||
|
data.commitTag !== process.env.commitTag)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{hasPermission(Permission.ADMIN) && data.restartRequired ? (
|
||||||
|
<Modal
|
||||||
|
iconSvg={<RefreshIcon />}
|
||||||
|
title={intl.formatMessage(messages.restartRequired)}
|
||||||
|
backgroundClickable={false}
|
||||||
|
onOk={() => {
|
||||||
|
setAlertDismissed(true);
|
||||||
|
if (data.commitTag !== process.env.commitTag) {
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
okText={intl.formatMessage(globalMessages.close)}
|
||||||
|
>
|
||||||
|
{intl.formatMessage(messages.restartRequiredDescription)}
|
||||||
|
</Modal>
|
||||||
|
) : (
|
||||||
|
<Modal
|
||||||
|
iconSvg={<SparklesIcon />}
|
||||||
|
title={intl.formatMessage(messages.appUpdated, {
|
||||||
|
applicationTitle: settings.currentSettings.applicationTitle,
|
||||||
|
})}
|
||||||
|
onOk={() => location.reload()}
|
||||||
|
okText={intl.formatMessage(messages.reloadApp, {
|
||||||
|
applicationTitle: settings.currentSettings.applicationTitle,
|
||||||
|
})}
|
||||||
|
backgroundClickable={false}
|
||||||
|
>
|
||||||
|
{intl.formatMessage(messages.appUpdatedDescription)}
|
||||||
|
</Modal>
|
||||||
|
)}
|
||||||
|
</Transition>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StatusChecker;
|
Loading…
Reference in new issue