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 prompt
This commit is contained in:
32
cypress/e2e/settings/general-settings.cy.ts
Normal file
32
cypress/e2e/settings/general-settings.cy.ts
Normal file
@@ -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');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -15,7 +15,7 @@ describe('User List', () => {
|
|||||||
cy.get('[data-testid=sidebar-toggle]').click();
|
cy.get('[data-testid=sidebar-toggle]').click();
|
||||||
cy.get('[data-testid=sidebar-menu-users-mobile]').click();
|
cy.get('[data-testid=sidebar-menu-users-mobile]').click();
|
||||||
|
|
||||||
cy.get('[data-testid=page-header').should('contain', 'User List');
|
cy.get('[data-testid=page-header]').should('contain', 'User List');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can find the admin user and friend user in the user list', () => {
|
it('can find the admin user and friend user in the user list', () => {
|
||||||
@@ -30,7 +30,7 @@ describe('User List', () => {
|
|||||||
|
|
||||||
cy.contains('Create Local User').click();
|
cy.contains('Create Local User').click();
|
||||||
|
|
||||||
cy.get('[data-testid=modal-title').should('contain', 'Create Local User');
|
cy.get('[data-testid=modal-title]').should('contain', 'Create Local User');
|
||||||
|
|
||||||
cy.get('#displayName').type(testUser.displayName);
|
cy.get('#displayName').type(testUser.displayName);
|
||||||
cy.get('#email').type(testUser.emailAddress);
|
cy.get('#email').type(testUser.emailAddress);
|
||||||
@@ -38,7 +38,7 @@ describe('User List', () => {
|
|||||||
|
|
||||||
cy.intercept('/api/v1/user?take=10&skip=0&sort=displayname').as('user');
|
cy.intercept('/api/v1/user?take=10&skip=0&sort=displayname').as('user');
|
||||||
|
|
||||||
cy.get('[data-testid=modal-ok-button').click();
|
cy.get('[data-testid=modal-ok-button]').click();
|
||||||
|
|
||||||
cy.wait('@user');
|
cy.wait('@user');
|
||||||
// Wait a little longer for the user list to fully re-render
|
// Wait a little longer for the user list to fully re-render
|
||||||
@@ -58,7 +58,7 @@ describe('User List', () => {
|
|||||||
|
|
||||||
cy.intercept('/api/v1/user?take=10&skip=0&sort=displayname').as('user');
|
cy.intercept('/api/v1/user?take=10&skip=0&sort=displayname').as('user');
|
||||||
|
|
||||||
cy.get('[data-testid=modal-ok-button').should('contain', 'Delete').click();
|
cy.get('[data-testid=modal-ok-button]').should('contain', 'Delete').click();
|
||||||
|
|
||||||
cy.wait('@user');
|
cy.wait('@user');
|
||||||
cy.wait(1000);
|
cy.wait(1000);
|
||||||
|
|||||||
@@ -1793,14 +1793,14 @@ components:
|
|||||||
paths:
|
paths:
|
||||||
/status:
|
/status:
|
||||||
get:
|
get:
|
||||||
summary: Get Overseerr version
|
summary: Get Overseerr status
|
||||||
description: Returns the current Overseerr version in a JSON object.
|
description: Returns the current Overseerr status in a JSON object.
|
||||||
security: []
|
security: []
|
||||||
tags:
|
tags:
|
||||||
- public
|
- public
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: Returned version
|
description: Returned status
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
@@ -1811,6 +1811,12 @@ paths:
|
|||||||
example: 1.0.0
|
example: 1.0.0
|
||||||
commitTag:
|
commitTag:
|
||||||
type: string
|
type: string
|
||||||
|
updateAvailable:
|
||||||
|
type: boolean
|
||||||
|
commitsBehind:
|
||||||
|
type: number
|
||||||
|
restartRequired:
|
||||||
|
type: boolean
|
||||||
/status/appdata:
|
/status/appdata:
|
||||||
get:
|
get:
|
||||||
summary: Get application data volume status
|
summary: Get application data volume status
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import { getSettings } from './lib/settings';
|
|||||||
import logger from './logger';
|
import logger from './logger';
|
||||||
import routes from './routes';
|
import routes from './routes';
|
||||||
import { getAppVersion } from './utils/appVersion';
|
import { getAppVersion } from './utils/appVersion';
|
||||||
|
import restartFlag from './utils/restartFlag';
|
||||||
|
|
||||||
const API_SPEC_PATH = path.join(__dirname, '../overseerr-api.yml');
|
const API_SPEC_PATH = path.join(__dirname, '../overseerr-api.yml');
|
||||||
|
|
||||||
@@ -53,6 +54,7 @@ app
|
|||||||
|
|
||||||
// Load Settings
|
// Load Settings
|
||||||
const settings = getSettings().load();
|
const settings = getSettings().load();
|
||||||
|
restartFlag.initializeSettings(settings.main);
|
||||||
|
|
||||||
// Migrate library types
|
// Migrate library types
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -56,4 +56,5 @@ export interface StatusResponse {
|
|||||||
commitTag: string;
|
commitTag: string;
|
||||||
updateAvailable: boolean;
|
updateAvailable: boolean;
|
||||||
commitsBehind: number;
|
commitsBehind: number;
|
||||||
|
restartRequired: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
export enum Permission {
|
export enum Permission {
|
||||||
NONE = 0,
|
NONE = 0,
|
||||||
ADMIN = 2,
|
ADMIN = 2,
|
||||||
MANAGE_SETTINGS = 4,
|
|
||||||
MANAGE_USERS = 8,
|
MANAGE_USERS = 8,
|
||||||
MANAGE_REQUESTS = 16,
|
MANAGE_REQUESTS = 16,
|
||||||
REQUEST = 32,
|
REQUEST = 32,
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { mapProductionCompany } from '../models/Movie';
|
|||||||
import { mapNetwork } from '../models/Tv';
|
import { mapNetwork } from '../models/Tv';
|
||||||
import { appDataPath, appDataStatus } from '../utils/appDataVolume';
|
import { appDataPath, appDataStatus } from '../utils/appDataVolume';
|
||||||
import { getAppVersion, getCommitTag } from '../utils/appVersion';
|
import { getAppVersion, getCommitTag } from '../utils/appVersion';
|
||||||
|
import restartFlag from '../utils/restartFlag';
|
||||||
import { isPerson } from '../utils/typeHelpers';
|
import { isPerson } from '../utils/typeHelpers';
|
||||||
import authRoutes from './auth';
|
import authRoutes from './auth';
|
||||||
import collectionRoutes from './collection';
|
import collectionRoutes from './collection';
|
||||||
@@ -78,6 +79,7 @@ router.get<unknown, StatusResponse>('/status', async (req, res) => {
|
|||||||
commitTag: getCommitTag(),
|
commitTag: getCommitTag(),
|
||||||
updateAvailable,
|
updateAvailable,
|
||||||
commitsBehind,
|
commitsBehind,
|
||||||
|
restartRequired: restartFlag.isSet(),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -100,11 +102,7 @@ router.get('/settings/public', async (req, res) => {
|
|||||||
return res.status(200).json(settings.fullPublicSettings);
|
return res.status(200).json(settings.fullPublicSettings);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
router.use(
|
router.use('/settings', isAuthenticated(Permission.ADMIN), settingsRoutes);
|
||||||
'/settings',
|
|
||||||
isAuthenticated(Permission.MANAGE_SETTINGS),
|
|
||||||
settingsRoutes
|
|
||||||
);
|
|
||||||
router.use('/search', isAuthenticated(), searchRoutes);
|
router.use('/search', isAuthenticated(), searchRoutes);
|
||||||
router.use('/discover', isAuthenticated(), discoverRoutes);
|
router.use('/discover', isAuthenticated(), discoverRoutes);
|
||||||
router.use('/request', isAuthenticated(), requestRoutes);
|
router.use('/request', isAuthenticated(), requestRoutes);
|
||||||
|
|||||||
@@ -258,12 +258,7 @@ export const canMakePermissionsChange = (
|
|||||||
user?: User
|
user?: User
|
||||||
): boolean =>
|
): boolean =>
|
||||||
// Only let the owner grant admin privileges
|
// Only let the owner grant admin privileges
|
||||||
!(hasPermission(Permission.ADMIN, permissions) && user?.id !== 1) ||
|
!(hasPermission(Permission.ADMIN, permissions) && user?.id !== 1);
|
||||||
// Only let users with the manage settings permission, grant the same permission
|
|
||||||
!(
|
|
||||||
hasPermission(Permission.MANAGE_SETTINGS, permissions) &&
|
|
||||||
!hasPermission(Permission.MANAGE_SETTINGS, user?.permissions ?? 0)
|
|
||||||
);
|
|
||||||
|
|
||||||
router.put<
|
router.put<
|
||||||
Record<string, never>,
|
Record<string, never>,
|
||||||
|
|||||||
23
server/utils/restartFlag.ts
Normal file
23
server/utils/restartFlag.ts
Normal file
@@ -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;
|
||||||
@@ -130,7 +130,7 @@ const Modal: React.FC<ModalProps> = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="relative overflow-x-hidden sm:flex sm:items-center">
|
<div className="relative overflow-x-hidden p-0.5 sm:flex sm:items-center">
|
||||||
{iconSvg && <div className="modal-icon">{iconSvg}</div>}
|
{iconSvg && <div className="modal-icon">{iconSvg}</div>}
|
||||||
<div
|
<div
|
||||||
className={`mt-3 truncate text-center text-white sm:mt-0 sm:text-left ${
|
className={`mt-3 truncate text-center text-white sm:mt-0 sm:text-left ${
|
||||||
@@ -149,7 +149,11 @@ const Modal: React.FC<ModalProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{children && (
|
{children && (
|
||||||
<div className="relative mt-4 text-sm leading-5 text-gray-300">
|
<div
|
||||||
|
className={`relative mt-4 text-sm leading-5 text-gray-300 ${
|
||||||
|
!(onCancel || onOk || onSecondary || onTertiary) ? 'mb-3' : ''
|
||||||
|
}`}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -80,7 +80,8 @@ const SidebarLinks: SidebarLinkProps[] = [
|
|||||||
messagesKey: 'settings',
|
messagesKey: 'settings',
|
||||||
svgIcon: <CogIcon className="mr-3 h-6 w-6" />,
|
svgIcon: <CogIcon className="mr-3 h-6 w-6" />,
|
||||||
activeRegExp: /^\/settings/,
|
activeRegExp: /^\/settings/,
|
||||||
requiredPermission: Permission.MANAGE_SETTINGS,
|
requiredPermission: Permission.ADMIN,
|
||||||
|
dataTestId: 'sidebar-menu-settings',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -12,9 +12,6 @@ export const messages = defineMessages({
|
|||||||
users: 'Manage Users',
|
users: 'Manage Users',
|
||||||
usersDescription:
|
usersDescription:
|
||||||
'Grant permission to manage users. Users with this permission cannot modify users with or grant the Admin privilege.',
|
'Grant permission to manage users. Users with this permission cannot modify users with or grant the Admin privilege.',
|
||||||
settings: 'Manage Settings',
|
|
||||||
settingsDescription:
|
|
||||||
'Grant permission to modify global settings. A user must have this permission to grant it to others.',
|
|
||||||
managerequests: 'Manage Requests',
|
managerequests: 'Manage Requests',
|
||||||
managerequestsDescription:
|
managerequestsDescription:
|
||||||
'Grant permission to manage media requests. All requests made by a user with this permission will be automatically approved.',
|
'Grant permission to manage media requests. All requests made by a user with this permission will be automatically approved.',
|
||||||
@@ -88,12 +85,6 @@ export const PermissionEdit: React.FC<PermissionEditProps> = ({
|
|||||||
description: intl.formatMessage(messages.adminDescription),
|
description: intl.formatMessage(messages.adminDescription),
|
||||||
permission: Permission.ADMIN,
|
permission: Permission.ADMIN,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: 'settings',
|
|
||||||
name: intl.formatMessage(messages.settings),
|
|
||||||
description: intl.formatMessage(messages.settingsDescription),
|
|
||||||
permission: Permission.MANAGE_SETTINGS,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: 'users',
|
id: 'users',
|
||||||
name: intl.formatMessage(messages.users),
|
name: intl.formatMessage(messages.users),
|
||||||
|
|||||||
@@ -67,14 +67,9 @@ const PermissionOption: React.FC<PermissionOptionProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
// Non-Admin users cannot modify the Admin permission
|
// Only the owner can modify the Admin permission
|
||||||
(actingUser &&
|
actingUser?.id !== 1 &&
|
||||||
!hasPermission(Permission.ADMIN, actingUser.permissions) &&
|
option.permission === Permission.ADMIN
|
||||||
option.permission === Permission.ADMIN) ||
|
|
||||||
// Users without the Manage Settings permission cannot modify/grant that permission
|
|
||||||
(actingUser &&
|
|
||||||
!hasPermission(Permission.MANAGE_SETTINGS, actingUser.permissions) &&
|
|
||||||
option.permission === Permission.MANAGE_SETTINGS)
|
|
||||||
) {
|
) {
|
||||||
disabled = true;
|
disabled = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,8 +41,7 @@ const messages = defineMessages({
|
|||||||
toastSettingsFailure: 'Something went wrong while saving settings.',
|
toastSettingsFailure: 'Something went wrong while saving settings.',
|
||||||
hideAvailable: 'Hide Available Media',
|
hideAvailable: 'Hide Available Media',
|
||||||
csrfProtection: 'Enable CSRF Protection',
|
csrfProtection: 'Enable CSRF Protection',
|
||||||
csrfProtectionTip:
|
csrfProtectionTip: 'Set external API access to read-only (requires HTTPS)',
|
||||||
'Set external API access to read-only (requires HTTPS, and Overseerr must be reloaded for changes to take effect)',
|
|
||||||
csrfProtectionHoverTip:
|
csrfProtectionHoverTip:
|
||||||
'Do NOT enable this setting unless you understand what you are doing!',
|
'Do NOT enable this setting unless you understand what you are doing!',
|
||||||
cacheImages: 'Enable Image Caching',
|
cacheImages: 'Enable Image Caching',
|
||||||
@@ -50,7 +49,7 @@ const messages = defineMessages({
|
|||||||
'Optimize and store all images locally (consumes a significant amount of disk space)',
|
'Optimize and store all images locally (consumes a significant amount of disk space)',
|
||||||
trustProxy: 'Enable Proxy Support',
|
trustProxy: 'Enable Proxy Support',
|
||||||
trustProxyTip:
|
trustProxyTip:
|
||||||
'Allow Overseerr to correctly register client IP addresses behind a proxy (Overseerr must be reloaded for changes to take effect)',
|
'Allow Overseerr to correctly register client IP addresses behind a proxy',
|
||||||
validationApplicationTitle: 'You must provide an application title',
|
validationApplicationTitle: 'You must provide an application title',
|
||||||
validationApplicationUrl: 'You must provide a valid URL',
|
validationApplicationUrl: 'You must provide a valid URL',
|
||||||
validationApplicationUrlTrailingSlash: 'URL must not end in a trailing slash',
|
validationApplicationUrlTrailingSlash: 'URL must not end in a trailing slash',
|
||||||
@@ -151,6 +150,7 @@ const SettingsMain: React.FC = () => {
|
|||||||
trustProxy: values.trustProxy,
|
trustProxy: values.trustProxy,
|
||||||
});
|
});
|
||||||
mutate('/api/v1/settings/public');
|
mutate('/api/v1/settings/public');
|
||||||
|
mutate('/api/v1/status');
|
||||||
|
|
||||||
if (setLocale) {
|
if (setLocale) {
|
||||||
setLocale(
|
setLocale(
|
||||||
@@ -252,7 +252,12 @@ const SettingsMain: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="form-row">
|
<div className="form-row">
|
||||||
<label htmlFor="trustProxy" className="checkbox-label">
|
<label htmlFor="trustProxy" className="checkbox-label">
|
||||||
<span>{intl.formatMessage(messages.trustProxy)}</span>
|
<span className="mr-2">
|
||||||
|
{intl.formatMessage(messages.trustProxy)}
|
||||||
|
</span>
|
||||||
|
<Badge badgeType="primary">
|
||||||
|
{intl.formatMessage(globalMessages.restartRequired)}
|
||||||
|
</Badge>
|
||||||
<span className="label-tip">
|
<span className="label-tip">
|
||||||
{intl.formatMessage(messages.trustProxyTip)}
|
{intl.formatMessage(messages.trustProxyTip)}
|
||||||
</span>
|
</span>
|
||||||
@@ -273,9 +278,12 @@ const SettingsMain: React.FC = () => {
|
|||||||
<span className="mr-2">
|
<span className="mr-2">
|
||||||
{intl.formatMessage(messages.csrfProtection)}
|
{intl.formatMessage(messages.csrfProtection)}
|
||||||
</span>
|
</span>
|
||||||
<Badge badgeType="danger">
|
<Badge badgeType="danger" className="mr-2">
|
||||||
{intl.formatMessage(globalMessages.advanced)}
|
{intl.formatMessage(globalMessages.advanced)}
|
||||||
</Badge>
|
</Badge>
|
||||||
|
<Badge badgeType="primary">
|
||||||
|
{intl.formatMessage(globalMessages.restartRequired)}
|
||||||
|
</Badge>
|
||||||
<span className="label-tip">
|
<span className="label-tip">
|
||||||
{intl.formatMessage(messages.csrfProtectionTip)}
|
{intl.formatMessage(messages.csrfProtectionTip)}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ const messages = defineMessages({
|
|||||||
'A 4K {serverType} server must be marked as default in order to enable users to submit 4K {mediaType} requests.',
|
'A 4K {serverType} server must be marked as default in order to enable users to submit 4K {mediaType} requests.',
|
||||||
mediaTypeMovie: 'movie',
|
mediaTypeMovie: 'movie',
|
||||||
mediaTypeSeries: 'series',
|
mediaTypeSeries: 'series',
|
||||||
|
deleteServer: 'Delete {serverType} Server',
|
||||||
});
|
});
|
||||||
|
|
||||||
interface ServerInstanceProps {
|
interface ServerInstanceProps {
|
||||||
@@ -256,7 +257,7 @@ const SettingsServices: React.FC = () => {
|
|||||||
leaveTo="opacity-0"
|
leaveTo="opacity-0"
|
||||||
>
|
>
|
||||||
<Modal
|
<Modal
|
||||||
okText="Delete"
|
okText={intl.formatMessage(globalMessages.delete)}
|
||||||
okButtonType="danger"
|
okButtonType="danger"
|
||||||
onOk={() => deleteServer()}
|
onOk={() => deleteServer()}
|
||||||
onCancel={() =>
|
onCancel={() =>
|
||||||
@@ -266,7 +267,10 @@ const SettingsServices: React.FC = () => {
|
|||||||
type: 'radarr',
|
type: 'radarr',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
title="Delete Server"
|
title={intl.formatMessage(messages.deleteServer, {
|
||||||
|
serverType:
|
||||||
|
deleteServerModal.type === 'radarr' ? 'Radarr' : 'Sonarr',
|
||||||
|
})}
|
||||||
iconSvg={<TrashIcon />}
|
iconSvg={<TrashIcon />}
|
||||||
>
|
>
|
||||||
{intl.formatMessage(messages.deleteserverconfirm)}
|
{intl.formatMessage(messages.deleteserverconfirm)}
|
||||||
|
|||||||
@@ -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;
|
|
||||||
94
src/components/StatusChecker/index.tsx
Normal file
94
src/components/StatusChecker/index.tsx
Normal file
@@ -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;
|
||||||
@@ -336,7 +336,7 @@ const UserList: React.FC = () => {
|
|||||||
type="warning"
|
type="warning"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{currentHasPermission(Permission.MANAGE_SETTINGS) &&
|
{currentHasPermission(Permission.ADMIN) &&
|
||||||
!passwordGenerationEnabled && (
|
!passwordGenerationEnabled && (
|
||||||
<Alert
|
<Alert
|
||||||
title={intl.formatMessage(
|
title={intl.formatMessage(
|
||||||
|
|||||||
@@ -54,10 +54,7 @@ const UserSettings: React.FC = ({ children }) => {
|
|||||||
regex: /\/settings\/password/,
|
regex: /\/settings\/password/,
|
||||||
hidden:
|
hidden:
|
||||||
(!settings.currentSettings.localLogin &&
|
(!settings.currentSettings.localLogin &&
|
||||||
!hasPermission(
|
!hasPermission(Permission.ADMIN, currentUser?.permissions ?? 0)) ||
|
||||||
Permission.MANAGE_SETTINGS,
|
|
||||||
currentUser?.permissions ?? 0
|
|
||||||
)) ||
|
|
||||||
(currentUser?.id !== 1 &&
|
(currentUser?.id !== 1 &&
|
||||||
currentUser?.id !== user?.id &&
|
currentUser?.id !== user?.id &&
|
||||||
hasPermission(Permission.ADMIN, user?.permissions ?? 0)),
|
hasPermission(Permission.ADMIN, user?.permissions ?? 0)),
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ const globalMessages = defineMessages({
|
|||||||
all: 'All',
|
all: 'All',
|
||||||
experimental: 'Experimental',
|
experimental: 'Experimental',
|
||||||
advanced: 'Advanced',
|
advanced: 'Advanced',
|
||||||
|
restartRequired: 'Restart Required',
|
||||||
loading: 'Loading…',
|
loading: 'Loading…',
|
||||||
settings: 'Settings',
|
settings: 'Settings',
|
||||||
usersettings: 'User Settings',
|
usersettings: 'User Settings',
|
||||||
|
|||||||
@@ -245,8 +245,6 @@
|
|||||||
"components.PermissionEdit.requestMoviesDescription": "Grant permission to submit requests for non-4K movies.",
|
"components.PermissionEdit.requestMoviesDescription": "Grant permission to submit requests for non-4K movies.",
|
||||||
"components.PermissionEdit.requestTv": "Request Series",
|
"components.PermissionEdit.requestTv": "Request Series",
|
||||||
"components.PermissionEdit.requestTvDescription": "Grant permission to submit requests for non-4K series.",
|
"components.PermissionEdit.requestTvDescription": "Grant permission to submit requests for non-4K series.",
|
||||||
"components.PermissionEdit.settings": "Manage Settings",
|
|
||||||
"components.PermissionEdit.settingsDescription": "Grant permission to modify global settings. A user must have this permission to grant it to others.",
|
|
||||||
"components.PermissionEdit.users": "Manage Users",
|
"components.PermissionEdit.users": "Manage Users",
|
||||||
"components.PermissionEdit.usersDescription": "Grant permission to manage users. Users with this permission cannot modify users with or grant the Admin privilege.",
|
"components.PermissionEdit.usersDescription": "Grant permission to manage users. Users with this permission cannot modify users with or grant the Admin privilege.",
|
||||||
"components.PermissionEdit.viewissues": "View Issues",
|
"components.PermissionEdit.viewissues": "View Issues",
|
||||||
@@ -711,10 +709,11 @@
|
|||||||
"components.Settings.copied": "Copied API key to clipboard.",
|
"components.Settings.copied": "Copied API key to clipboard.",
|
||||||
"components.Settings.csrfProtection": "Enable CSRF Protection",
|
"components.Settings.csrfProtection": "Enable CSRF Protection",
|
||||||
"components.Settings.csrfProtectionHoverTip": "Do NOT enable this setting unless you understand what you are doing!",
|
"components.Settings.csrfProtectionHoverTip": "Do NOT enable this setting unless you understand what you are doing!",
|
||||||
"components.Settings.csrfProtectionTip": "Set external API access to read-only (requires HTTPS, and Overseerr must be reloaded for changes to take effect)",
|
"components.Settings.csrfProtectionTip": "Set external API access to read-only (requires HTTPS)",
|
||||||
"components.Settings.currentlibrary": "Current Library: {name}",
|
"components.Settings.currentlibrary": "Current Library: {name}",
|
||||||
"components.Settings.default": "Default",
|
"components.Settings.default": "Default",
|
||||||
"components.Settings.default4k": "Default 4K",
|
"components.Settings.default4k": "Default 4K",
|
||||||
|
"components.Settings.deleteServer": "Delete {serverType} Server",
|
||||||
"components.Settings.deleteserverconfirm": "Are you sure you want to delete this server?",
|
"components.Settings.deleteserverconfirm": "Are you sure you want to delete this server?",
|
||||||
"components.Settings.email": "Email",
|
"components.Settings.email": "Email",
|
||||||
"components.Settings.enablessl": "Use SSL",
|
"components.Settings.enablessl": "Use SSL",
|
||||||
@@ -789,7 +788,7 @@
|
|||||||
"components.Settings.toastTautulliSettingsFailure": "Something went wrong while saving Tautulli settings.",
|
"components.Settings.toastTautulliSettingsFailure": "Something went wrong while saving Tautulli settings.",
|
||||||
"components.Settings.toastTautulliSettingsSuccess": "Tautulli settings saved successfully!",
|
"components.Settings.toastTautulliSettingsSuccess": "Tautulli settings saved successfully!",
|
||||||
"components.Settings.trustProxy": "Enable Proxy Support",
|
"components.Settings.trustProxy": "Enable Proxy Support",
|
||||||
"components.Settings.trustProxyTip": "Allow Overseerr to correctly register client IP addresses behind a proxy (Overseerr must be reloaded for changes to take effect)",
|
"components.Settings.trustProxyTip": "Allow Overseerr to correctly register client IP addresses behind a proxy",
|
||||||
"components.Settings.urlBase": "URL Base",
|
"components.Settings.urlBase": "URL Base",
|
||||||
"components.Settings.validationApiKey": "You must provide an API key",
|
"components.Settings.validationApiKey": "You must provide an API key",
|
||||||
"components.Settings.validationApplicationTitle": "You must provide an application title",
|
"components.Settings.validationApplicationTitle": "You must provide an application title",
|
||||||
@@ -818,9 +817,11 @@
|
|||||||
"components.Setup.welcome": "Welcome to Overseerr",
|
"components.Setup.welcome": "Welcome to Overseerr",
|
||||||
"components.StatusBadge.status": "{status}",
|
"components.StatusBadge.status": "{status}",
|
||||||
"components.StatusBadge.status4k": "4K {status}",
|
"components.StatusBadge.status4k": "4K {status}",
|
||||||
"components.StatusChacker.newversionDescription": "Overseerr has been updated! Please click the button below to reload the page.",
|
"components.StatusChecker.appUpdated": "{applicationTitle} Updated",
|
||||||
"components.StatusChacker.newversionavailable": "Application Update",
|
"components.StatusChecker.appUpdatedDescription": "Please click the button below to reload the application.",
|
||||||
"components.StatusChacker.reloadOverseerr": "Reload",
|
"components.StatusChecker.reloadApp": "Reload {applicationTitle}",
|
||||||
|
"components.StatusChecker.restartRequired": "Server Restart Required",
|
||||||
|
"components.StatusChecker.restartRequiredDescription": "Please restart the server to apply the updated settings.",
|
||||||
"components.TvDetails.TvCast.fullseriescast": "Full Series Cast",
|
"components.TvDetails.TvCast.fullseriescast": "Full Series Cast",
|
||||||
"components.TvDetails.TvCrew.fullseriescrew": "Full Series Crew",
|
"components.TvDetails.TvCrew.fullseriescrew": "Full Series Crew",
|
||||||
"components.TvDetails.anime": "Anime",
|
"components.TvDetails.anime": "Anime",
|
||||||
@@ -1020,6 +1021,7 @@
|
|||||||
"i18n.requested": "Requested",
|
"i18n.requested": "Requested",
|
||||||
"i18n.requesting": "Requesting…",
|
"i18n.requesting": "Requesting…",
|
||||||
"i18n.resolved": "Resolved",
|
"i18n.resolved": "Resolved",
|
||||||
|
"i18n.restartRequired": "Restart Required",
|
||||||
"i18n.resultsperpage": "Display {pageSize} results per page",
|
"i18n.resultsperpage": "Display {pageSize} results per page",
|
||||||
"i18n.retry": "Retry",
|
"i18n.retry": "Retry",
|
||||||
"i18n.retrying": "Retrying…",
|
"i18n.retrying": "Retrying…",
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import Layout from '../components/Layout';
|
|||||||
import LoadingBar from '../components/LoadingBar';
|
import LoadingBar from '../components/LoadingBar';
|
||||||
import PWAHeader from '../components/PWAHeader';
|
import PWAHeader from '../components/PWAHeader';
|
||||||
import ServiceWorkerSetup from '../components/ServiceWorkerSetup';
|
import ServiceWorkerSetup from '../components/ServiceWorkerSetup';
|
||||||
import StatusChecker from '../components/StatusChacker';
|
import StatusChecker from '../components/StatusChecker';
|
||||||
import Toast from '../components/Toast';
|
import Toast from '../components/Toast';
|
||||||
import ToastContainer from '../components/ToastContainer';
|
import ToastContainer from '../components/ToastContainer';
|
||||||
import { InteractionProvider } from '../context/InteractionContext';
|
import { InteractionProvider } from '../context/InteractionContext';
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import useRouteGuard from '../../hooks/useRouteGuard';
|
|||||||
import { Permission } from '../../hooks/useUser';
|
import { Permission } from '../../hooks/useUser';
|
||||||
|
|
||||||
const SettingsAboutPage: NextPage = () => {
|
const SettingsAboutPage: NextPage = () => {
|
||||||
useRouteGuard(Permission.MANAGE_SETTINGS);
|
useRouteGuard(Permission.ADMIN);
|
||||||
return (
|
return (
|
||||||
<SettingsLayout>
|
<SettingsLayout>
|
||||||
<SettingsAbout />
|
<SettingsAbout />
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import useRouteGuard from '../../hooks/useRouteGuard';
|
|||||||
import { Permission } from '../../hooks/useUser';
|
import { Permission } from '../../hooks/useUser';
|
||||||
|
|
||||||
const SettingsPage: NextPage = () => {
|
const SettingsPage: NextPage = () => {
|
||||||
useRouteGuard(Permission.MANAGE_SETTINGS);
|
useRouteGuard(Permission.ADMIN);
|
||||||
return (
|
return (
|
||||||
<SettingsLayout>
|
<SettingsLayout>
|
||||||
<SettingsMain />
|
<SettingsMain />
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import React from 'react';
|
|
||||||
import type { NextPage } from 'next';
|
import type { NextPage } from 'next';
|
||||||
import SettingsLayout from '../../components/Settings/SettingsLayout';
|
import React from 'react';
|
||||||
import SettingsJobs from '../../components/Settings/SettingsJobsCache';
|
import SettingsJobs from '../../components/Settings/SettingsJobsCache';
|
||||||
import { Permission } from '../../hooks/useUser';
|
import SettingsLayout from '../../components/Settings/SettingsLayout';
|
||||||
import useRouteGuard from '../../hooks/useRouteGuard';
|
import useRouteGuard from '../../hooks/useRouteGuard';
|
||||||
|
import { Permission } from '../../hooks/useUser';
|
||||||
|
|
||||||
const SettingsMainPage: NextPage = () => {
|
const SettingsMainPage: NextPage = () => {
|
||||||
useRouteGuard(Permission.MANAGE_SETTINGS);
|
useRouteGuard(Permission.ADMIN);
|
||||||
return (
|
return (
|
||||||
<SettingsLayout>
|
<SettingsLayout>
|
||||||
<SettingsJobs />
|
<SettingsJobs />
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import useRouteGuard from '../../hooks/useRouteGuard';
|
|||||||
import { Permission } from '../../hooks/useUser';
|
import { Permission } from '../../hooks/useUser';
|
||||||
|
|
||||||
const SettingsLogsPage: NextPage = () => {
|
const SettingsLogsPage: NextPage = () => {
|
||||||
useRouteGuard(Permission.MANAGE_SETTINGS);
|
useRouteGuard(Permission.ADMIN);
|
||||||
return (
|
return (
|
||||||
<SettingsLayout>
|
<SettingsLayout>
|
||||||
<SettingsLogs />
|
<SettingsLogs />
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ import React from 'react';
|
|||||||
import type { NextPage } from 'next';
|
import type { NextPage } from 'next';
|
||||||
import SettingsLayout from '../../components/Settings/SettingsLayout';
|
import SettingsLayout from '../../components/Settings/SettingsLayout';
|
||||||
import SettingsMain from '../../components/Settings/SettingsMain';
|
import SettingsMain from '../../components/Settings/SettingsMain';
|
||||||
import { Permission } from '../../hooks/useUser';
|
|
||||||
import useRouteGuard from '../../hooks/useRouteGuard';
|
import useRouteGuard from '../../hooks/useRouteGuard';
|
||||||
|
import { Permission } from '../../hooks/useUser';
|
||||||
|
|
||||||
const SettingsMainPage: NextPage = () => {
|
const SettingsMainPage: NextPage = () => {
|
||||||
useRouteGuard(Permission.MANAGE_SETTINGS);
|
useRouteGuard(Permission.ADMIN);
|
||||||
return (
|
return (
|
||||||
<SettingsLayout>
|
<SettingsLayout>
|
||||||
<SettingsMain />
|
<SettingsMain />
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import useRouteGuard from '../../../hooks/useRouteGuard';
|
|||||||
import { Permission } from '../../../hooks/useUser';
|
import { Permission } from '../../../hooks/useUser';
|
||||||
|
|
||||||
const NotificationsPage: NextPage = () => {
|
const NotificationsPage: NextPage = () => {
|
||||||
useRouteGuard(Permission.MANAGE_SETTINGS);
|
useRouteGuard(Permission.ADMIN);
|
||||||
return (
|
return (
|
||||||
<SettingsLayout>
|
<SettingsLayout>
|
||||||
<SettingsNotifications>
|
<SettingsNotifications>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import useRouteGuard from '../../../hooks/useRouteGuard';
|
|||||||
import { Permission } from '../../../hooks/useUser';
|
import { Permission } from '../../../hooks/useUser';
|
||||||
|
|
||||||
const NotificationsPage: NextPage = () => {
|
const NotificationsPage: NextPage = () => {
|
||||||
useRouteGuard(Permission.MANAGE_SETTINGS);
|
useRouteGuard(Permission.ADMIN);
|
||||||
return (
|
return (
|
||||||
<SettingsLayout>
|
<SettingsLayout>
|
||||||
<SettingsNotifications>
|
<SettingsNotifications>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import useRouteGuard from '../../../hooks/useRouteGuard';
|
|||||||
import { Permission } from '../../../hooks/useUser';
|
import { Permission } from '../../../hooks/useUser';
|
||||||
|
|
||||||
const NotificationsPage: NextPage = () => {
|
const NotificationsPage: NextPage = () => {
|
||||||
useRouteGuard(Permission.MANAGE_SETTINGS);
|
useRouteGuard(Permission.ADMIN);
|
||||||
return (
|
return (
|
||||||
<SettingsLayout>
|
<SettingsLayout>
|
||||||
<SettingsNotifications>
|
<SettingsNotifications>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import useRouteGuard from '../../../hooks/useRouteGuard';
|
|||||||
import { Permission } from '../../../hooks/useUser';
|
import { Permission } from '../../../hooks/useUser';
|
||||||
|
|
||||||
const NotificationsPage: NextPage = () => {
|
const NotificationsPage: NextPage = () => {
|
||||||
useRouteGuard(Permission.MANAGE_SETTINGS);
|
useRouteGuard(Permission.ADMIN);
|
||||||
return (
|
return (
|
||||||
<SettingsLayout>
|
<SettingsLayout>
|
||||||
<SettingsNotifications>
|
<SettingsNotifications>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import useRouteGuard from '../../../hooks/useRouteGuard';
|
|||||||
import { Permission } from '../../../hooks/useUser';
|
import { Permission } from '../../../hooks/useUser';
|
||||||
|
|
||||||
const NotificationsPage: NextPage = () => {
|
const NotificationsPage: NextPage = () => {
|
||||||
useRouteGuard(Permission.MANAGE_SETTINGS);
|
useRouteGuard(Permission.ADMIN);
|
||||||
return (
|
return (
|
||||||
<SettingsLayout>
|
<SettingsLayout>
|
||||||
<SettingsNotifications>
|
<SettingsNotifications>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import useRouteGuard from '../../../hooks/useRouteGuard';
|
|||||||
import { Permission } from '../../../hooks/useUser';
|
import { Permission } from '../../../hooks/useUser';
|
||||||
|
|
||||||
const NotificationsPage: NextPage = () => {
|
const NotificationsPage: NextPage = () => {
|
||||||
useRouteGuard(Permission.MANAGE_SETTINGS);
|
useRouteGuard(Permission.ADMIN);
|
||||||
return (
|
return (
|
||||||
<SettingsLayout>
|
<SettingsLayout>
|
||||||
<SettingsNotifications>
|
<SettingsNotifications>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import useRouteGuard from '../../../hooks/useRouteGuard';
|
|||||||
import { Permission } from '../../../hooks/useUser';
|
import { Permission } from '../../../hooks/useUser';
|
||||||
|
|
||||||
const NotificationsSlackPage: NextPage = () => {
|
const NotificationsSlackPage: NextPage = () => {
|
||||||
useRouteGuard(Permission.MANAGE_SETTINGS);
|
useRouteGuard(Permission.ADMIN);
|
||||||
return (
|
return (
|
||||||
<SettingsLayout>
|
<SettingsLayout>
|
||||||
<SettingsNotifications>
|
<SettingsNotifications>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import useRouteGuard from '../../../hooks/useRouteGuard';
|
|||||||
import { Permission } from '../../../hooks/useUser';
|
import { Permission } from '../../../hooks/useUser';
|
||||||
|
|
||||||
const NotificationsPage: NextPage = () => {
|
const NotificationsPage: NextPage = () => {
|
||||||
useRouteGuard(Permission.MANAGE_SETTINGS);
|
useRouteGuard(Permission.ADMIN);
|
||||||
return (
|
return (
|
||||||
<SettingsLayout>
|
<SettingsLayout>
|
||||||
<SettingsNotifications>
|
<SettingsNotifications>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import useRouteGuard from '../../../hooks/useRouteGuard';
|
|||||||
import { Permission } from '../../../hooks/useUser';
|
import { Permission } from '../../../hooks/useUser';
|
||||||
|
|
||||||
const NotificationsPage: NextPage = () => {
|
const NotificationsPage: NextPage = () => {
|
||||||
useRouteGuard(Permission.MANAGE_SETTINGS);
|
useRouteGuard(Permission.ADMIN);
|
||||||
return (
|
return (
|
||||||
<SettingsLayout>
|
<SettingsLayout>
|
||||||
<SettingsNotifications>
|
<SettingsNotifications>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import useRouteGuard from '../../../hooks/useRouteGuard';
|
|||||||
import { Permission } from '../../../hooks/useUser';
|
import { Permission } from '../../../hooks/useUser';
|
||||||
|
|
||||||
const NotificationsWebPushPage: NextPage = () => {
|
const NotificationsWebPushPage: NextPage = () => {
|
||||||
useRouteGuard(Permission.MANAGE_SETTINGS);
|
useRouteGuard(Permission.ADMIN);
|
||||||
return (
|
return (
|
||||||
<SettingsLayout>
|
<SettingsLayout>
|
||||||
<SettingsNotifications>
|
<SettingsNotifications>
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import React from 'react';
|
|
||||||
import type { NextPage } from 'next';
|
import type { NextPage } from 'next';
|
||||||
|
import React from 'react';
|
||||||
import SettingsLayout from '../../components/Settings/SettingsLayout';
|
import SettingsLayout from '../../components/Settings/SettingsLayout';
|
||||||
import SettingsPlex from '../../components/Settings/SettingsPlex';
|
import SettingsPlex from '../../components/Settings/SettingsPlex';
|
||||||
import { Permission } from '../../hooks/useUser';
|
|
||||||
import useRouteGuard from '../../hooks/useRouteGuard';
|
import useRouteGuard from '../../hooks/useRouteGuard';
|
||||||
|
import { Permission } from '../../hooks/useUser';
|
||||||
|
|
||||||
const PlexSettingsPage: NextPage = () => {
|
const PlexSettingsPage: NextPage = () => {
|
||||||
useRouteGuard(Permission.MANAGE_SETTINGS);
|
useRouteGuard(Permission.ADMIN);
|
||||||
return (
|
return (
|
||||||
<SettingsLayout>
|
<SettingsLayout>
|
||||||
<SettingsPlex />
|
<SettingsPlex />
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import React from 'react';
|
|
||||||
import type { NextPage } from 'next';
|
import type { NextPage } from 'next';
|
||||||
|
import React from 'react';
|
||||||
import SettingsLayout from '../../components/Settings/SettingsLayout';
|
import SettingsLayout from '../../components/Settings/SettingsLayout';
|
||||||
import SettingsServices from '../../components/Settings/SettingsServices';
|
import SettingsServices from '../../components/Settings/SettingsServices';
|
||||||
import { Permission } from '../../hooks/useUser';
|
|
||||||
import useRouteGuard from '../../hooks/useRouteGuard';
|
import useRouteGuard from '../../hooks/useRouteGuard';
|
||||||
|
import { Permission } from '../../hooks/useUser';
|
||||||
|
|
||||||
const ServicesSettingsPage: NextPage = () => {
|
const ServicesSettingsPage: NextPage = () => {
|
||||||
useRouteGuard(Permission.MANAGE_SETTINGS);
|
useRouteGuard(Permission.ADMIN);
|
||||||
return (
|
return (
|
||||||
<SettingsLayout>
|
<SettingsLayout>
|
||||||
<SettingsServices />
|
<SettingsServices />
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ import React from 'react';
|
|||||||
import type { NextPage } from 'next';
|
import type { NextPage } from 'next';
|
||||||
import SettingsLayout from '../../components/Settings/SettingsLayout';
|
import SettingsLayout from '../../components/Settings/SettingsLayout';
|
||||||
import SettingsUsers from '../../components/Settings/SettingsUsers';
|
import SettingsUsers from '../../components/Settings/SettingsUsers';
|
||||||
import { Permission } from '../../hooks/useUser';
|
|
||||||
import useRouteGuard from '../../hooks/useRouteGuard';
|
import useRouteGuard from '../../hooks/useRouteGuard';
|
||||||
|
import { Permission } from '../../hooks/useUser';
|
||||||
|
|
||||||
const SettingsUsersPage: NextPage = () => {
|
const SettingsUsersPage: NextPage = () => {
|
||||||
useRouteGuard(Permission.MANAGE_SETTINGS);
|
useRouteGuard(Permission.ADMIN);
|
||||||
return (
|
return (
|
||||||
<SettingsLayout>
|
<SettingsLayout>
|
||||||
<SettingsUsers />
|
<SettingsUsers />
|
||||||
|
|||||||
Reference in New Issue
Block a user