1
0
Fork 0
mirror of https://github.com/jellyfin/jellyfin-web synced 2025-03-30 19:56:21 +00:00
jellyfin-web/src/apps/dashboard/routes/users/access.tsx

334 lines
14 KiB
TypeScript
Raw Normal View History

import type { BaseItemDto, DeviceInfoDto, UserDto } from '@jellyfin/sdk/lib/generated-client';
2024-09-28 14:57:45 +03:00
import React, { useCallback, useEffect, useState, useRef, useMemo } from 'react';
import { useSearchParams } from 'react-router-dom';
2021-10-16 00:26:31 +03:00
2023-05-01 16:50:41 -04:00
import loading from '../../../../components/loading/loading';
2024-08-14 13:31:34 -04:00
import globalize from '../../../../lib/globalize';
2023-05-01 16:50:41 -04:00
import toast from '../../../../components/toast/toast';
import SectionTabs from '../../../../components/dashboard/users/SectionTabs';
import ButtonElement from '../../../../elements/ButtonElement';
import SectionTitleContainer from '../../../../elements/SectionTitleContainer';
import AccessContainer from '../../../../components/dashboard/users/AccessContainer';
import CheckBoxElement from '../../../../elements/CheckBoxElement';
import Page from '../../../../components/Page';
2021-10-16 00:26:31 +03:00
type ItemsArr = {
2024-06-02 20:58:11 +03:00
Name?: string | null;
Id?: string | null;
AppName?: string | null;
2024-09-04 11:28:57 +03:00
CustomName?: string | null;
2021-10-16 00:26:31 +03:00
checkedAttribute?: string
2023-05-02 15:54:53 -04:00
};
2021-10-16 00:26:31 +03:00
2024-06-02 20:58:11 +03:00
const UserLibraryAccess = () => {
const [ searchParams ] = useSearchParams();
const userId = searchParams.get('userId');
2021-10-16 00:26:31 +03:00
const [ userName, setUserName ] = useState('');
2022-02-15 23:49:46 +03:00
const [channelsItems, setChannelsItems] = useState<ItemsArr[]>([]);
const [mediaFoldersItems, setMediaFoldersItems] = useState<ItemsArr[]>([]);
const [devicesItems, setDevicesItems] = useState<ItemsArr[]>([]);
2024-09-28 14:57:45 +03:00
const libraryMenu = useMemo(async () => ((await import('../../../../scripts/libraryMenu')).default), []);
2021-10-16 00:26:31 +03:00
2022-02-15 23:47:59 +03:00
const element = useRef<HTMLDivElement>(null);
2021-10-16 00:26:31 +03:00
2022-02-18 14:27:39 +03:00
const triggerChange = (select: HTMLInputElement) => {
const evt = new Event('change', { bubbles: false, cancelable: true });
2021-11-13 22:10:34 +03:00
select.dispatchEvent(evt);
};
2024-06-02 20:58:11 +03:00
const loadMediaFolders = useCallback((user: UserDto, mediaFolders: BaseItemDto[]) => {
const page = element.current;
if (!page) {
console.error('[userlibraryaccess] Unexpected null page reference');
return;
}
2021-11-13 22:10:34 +03:00
const itemsArr: ItemsArr[] = [];
for (const folder of mediaFolders) {
2024-06-02 20:58:11 +03:00
const isChecked = user.Policy?.EnableAllFolders || user.Policy?.EnabledFolders?.indexOf(folder.Id || '') != -1;
2021-11-13 22:10:34 +03:00
const checkedAttribute = isChecked ? ' checked="checked"' : '';
itemsArr.push({
Id: folder.Id,
Name: folder.Name,
checkedAttribute: checkedAttribute
2021-10-16 00:26:31 +03:00
});
2021-11-13 22:10:34 +03:00
}
2021-10-16 00:26:31 +03:00
2021-11-13 22:10:34 +03:00
setMediaFoldersItems(itemsArr);
2021-10-16 00:26:31 +03:00
2022-02-16 22:01:13 +03:00
const chkEnableAllFolders = page.querySelector('.chkEnableAllFolders') as HTMLInputElement;
2024-06-02 20:58:11 +03:00
chkEnableAllFolders.checked = Boolean(user.Policy?.EnableAllFolders);
2021-11-13 22:10:34 +03:00
triggerChange(chkEnableAllFolders);
}, []);
2021-10-16 00:26:31 +03:00
2024-06-02 20:58:11 +03:00
const loadChannels = useCallback((user: UserDto, channels: BaseItemDto[]) => {
const page = element.current;
if (!page) {
console.error('[userlibraryaccess] Unexpected null page reference');
return;
}
2021-11-13 22:10:34 +03:00
const itemsArr: ItemsArr[] = [];
for (const folder of channels) {
2024-06-02 20:58:11 +03:00
const isChecked = user.Policy?.EnableAllChannels || user.Policy?.EnabledChannels?.indexOf(folder.Id || '') != -1;
2021-11-13 22:10:34 +03:00
const checkedAttribute = isChecked ? ' checked="checked"' : '';
itemsArr.push({
Id: folder.Id,
Name: folder.Name,
checkedAttribute: checkedAttribute
});
}
2021-10-16 00:26:31 +03:00
2021-11-13 22:10:34 +03:00
setChannelsItems(itemsArr);
2021-10-16 00:26:31 +03:00
2021-11-13 22:10:34 +03:00
if (channels.length) {
2022-02-16 22:01:13 +03:00
(page.querySelector('.channelAccessContainer') as HTMLDivElement).classList.remove('hide');
2021-11-13 22:10:34 +03:00
} else {
2022-02-16 22:01:13 +03:00
(page.querySelector('.channelAccessContainer') as HTMLDivElement).classList.add('hide');
2021-11-13 22:10:34 +03:00
}
2021-10-16 00:26:31 +03:00
2022-02-16 22:01:13 +03:00
const chkEnableAllChannels = page.querySelector('.chkEnableAllChannels') as HTMLInputElement;
2024-06-02 20:58:11 +03:00
chkEnableAllChannels.checked = Boolean(user.Policy?.EnableAllChannels);
2021-11-13 22:10:34 +03:00
triggerChange(chkEnableAllChannels);
}, []);
2021-10-16 00:26:31 +03:00
const loadDevices = useCallback((user: UserDto, devices: DeviceInfoDto[]) => {
const page = element.current;
if (!page) {
console.error('[userlibraryaccess] Unexpected null page reference');
return;
}
2021-11-13 22:10:34 +03:00
const itemsArr: ItemsArr[] = [];
for (const device of devices) {
2024-06-02 20:58:11 +03:00
const isChecked = user.Policy?.EnableAllDevices || user.Policy?.EnabledDevices?.indexOf(device.Id || '') != -1;
2021-11-13 22:10:34 +03:00
const checkedAttribute = isChecked ? ' checked="checked"' : '';
itemsArr.push({
Id: device.Id,
Name: device.Name,
AppName: device.AppName,
2024-09-04 11:28:57 +03:00
CustomName: device.CustomName,
2021-11-13 22:10:34 +03:00
checkedAttribute: checkedAttribute
});
}
2021-10-16 00:26:31 +03:00
2021-11-13 22:10:34 +03:00
setDevicesItems(itemsArr);
2021-10-16 00:26:31 +03:00
2022-02-16 22:01:13 +03:00
const chkEnableAllDevices = page.querySelector('.chkEnableAllDevices') as HTMLInputElement;
2024-06-02 20:58:11 +03:00
chkEnableAllDevices.checked = Boolean(user.Policy?.EnableAllDevices);
2021-11-13 22:10:34 +03:00
triggerChange(chkEnableAllDevices);
2021-10-16 00:26:31 +03:00
2024-06-02 20:58:11 +03:00
if (user.Policy?.IsAdministrator) {
2022-02-16 22:01:13 +03:00
(page.querySelector('.deviceAccessContainer') as HTMLDivElement).classList.add('hide');
2021-11-13 22:10:34 +03:00
} else {
2022-02-16 22:01:13 +03:00
(page.querySelector('.deviceAccessContainer') as HTMLDivElement).classList.remove('hide');
2021-11-13 22:10:34 +03:00
}
}, []);
2021-10-16 00:26:31 +03:00
const loadUser = useCallback((user: UserDto, mediaFolders: BaseItemDto[], channels: BaseItemDto[], devices: DeviceInfoDto[]) => {
2024-06-02 20:58:11 +03:00
setUserName(user.Name || '');
2024-09-28 14:57:45 +03:00
void libraryMenu.then(menu => menu.setTitle(user.Name));
2021-11-13 22:10:34 +03:00
loadChannels(user, channels);
loadMediaFolders(user, mediaFolders);
loadDevices(user, devices);
loading.hide();
}, [loadChannels, loadDevices, loadMediaFolders]);
const loadData = useCallback(() => {
loading.show();
const promise1 = userId ? window.ApiClient.getUser(userId) : Promise.resolve({ Configuration: {} });
const promise2 = window.ApiClient.getJSON(window.ApiClient.getUrl('Library/MediaFolders', {
IsHidden: false
}));
const promise3 = window.ApiClient.getJSON(window.ApiClient.getUrl('Channels'));
const promise4 = window.ApiClient.getJSON(window.ApiClient.getUrl('Devices'));
Promise.all([promise1, promise2, promise3, promise4]).then(function (responses) {
loadUser(responses[0], responses[1].Items, responses[2].Items, responses[3].Items);
2023-05-02 11:24:53 -04:00
}).catch(err => {
console.error('[userlibraryaccess] failed to load data', err);
2021-11-13 22:10:34 +03:00
});
}, [loadUser, userId]);
2021-10-16 00:26:31 +03:00
2021-11-13 22:10:34 +03:00
useEffect(() => {
const page = element.current;
if (!page) {
console.error('[userlibraryaccess] Unexpected null page reference');
return;
}
2021-11-13 22:10:34 +03:00
loadData();
2021-10-16 00:26:31 +03:00
2022-02-18 14:27:39 +03:00
const onSubmit = (e: Event) => {
if (!userId) {
console.error('[userlibraryaccess] missing user id');
return;
}
2021-10-16 00:26:31 +03:00
loading.show();
window.ApiClient.getUser(userId).then(function (result) {
saveUser(result);
2023-05-02 11:24:53 -04:00
}).catch(err => {
console.error('[userlibraryaccess] failed to fetch user', err);
2021-10-16 00:26:31 +03:00
});
e.preventDefault();
e.stopPropagation();
return false;
};
2022-02-18 14:27:39 +03:00
const saveUser = (user: UserDto) => {
if (!user.Id) {
throw new Error('Unexpected null user.Id');
}
if (!user.Policy) {
throw new Error('Unexpected null user.Policy');
}
2022-02-16 22:01:13 +03:00
user.Policy.EnableAllFolders = (page.querySelector('.chkEnableAllFolders') as HTMLInputElement).checked;
user.Policy.EnabledFolders = user.Policy.EnableAllFolders ? [] : Array.prototype.filter.call(page.querySelectorAll('.chkFolder'), function (c) {
2021-10-16 00:26:31 +03:00
return c.checked;
}).map(function (c) {
return c.getAttribute('data-id');
});
2022-02-16 22:01:13 +03:00
user.Policy.EnableAllChannels = (page.querySelector('.chkEnableAllChannels') as HTMLInputElement).checked;
user.Policy.EnabledChannels = user.Policy.EnableAllChannels ? [] : Array.prototype.filter.call(page.querySelectorAll('.chkChannel'), function (c) {
2021-10-16 00:26:31 +03:00
return c.checked;
}).map(function (c) {
return c.getAttribute('data-id');
});
2022-02-16 22:01:13 +03:00
user.Policy.EnableAllDevices = (page.querySelector('.chkEnableAllDevices') as HTMLInputElement).checked;
user.Policy.EnabledDevices = user.Policy.EnableAllDevices ? [] : Array.prototype.filter.call(page.querySelectorAll('.chkDevice'), function (c) {
2021-10-16 00:26:31 +03:00
return c.checked;
}).map(function (c) {
return c.getAttribute('data-id');
});
user.Policy.BlockedChannels = null;
user.Policy.BlockedMediaFolders = null;
window.ApiClient.updateUserPolicy(user.Id, user.Policy).then(function () {
onSaveComplete();
2023-05-02 11:24:53 -04:00
}).catch(err => {
console.error('[userlibraryaccess] failed to update user policy', err);
2021-10-16 00:26:31 +03:00
});
};
const onSaveComplete = () => {
loading.hide();
toast(globalize.translate('SettingsSaved'));
};
2022-02-16 22:01:13 +03:00
(page.querySelector('.chkEnableAllDevices') as HTMLInputElement).addEventListener('change', function (this: HTMLInputElement) {
(page.querySelector('.deviceAccessListContainer') as HTMLDivElement).classList.toggle('hide', this.checked);
2021-10-16 00:26:31 +03:00
});
2022-02-16 22:01:13 +03:00
(page.querySelector('.chkEnableAllChannels') as HTMLInputElement).addEventListener('change', function (this: HTMLInputElement) {
(page.querySelector('.channelAccessListContainer') as HTMLDivElement).classList.toggle('hide', this.checked);
2021-10-16 00:26:31 +03:00
});
2022-02-16 22:01:13 +03:00
(page.querySelector('.chkEnableAllFolders') as HTMLInputElement).addEventListener('change', function (this: HTMLInputElement) {
(page.querySelector('.folderAccessListContainer') as HTMLDivElement).classList.toggle('hide', this.checked);
2021-10-16 00:26:31 +03:00
});
2022-02-16 22:01:13 +03:00
(page.querySelector('.userLibraryAccessForm') as HTMLFormElement).addEventListener('submit', onSubmit);
2021-11-13 22:10:34 +03:00
}, [loadData]);
2021-10-16 00:26:31 +03:00
return (
2022-06-29 03:21:45 +03:00
<Page
id='userLibraryAccessPage'
className='mainAnimatedPage type-interior'
>
<div ref={element} className='content-primary'>
2022-06-29 02:17:10 +03:00
<div className='verticalSection'>
<SectionTitleContainer
title={userName}
url='https://jellyfin.org/docs/general/server/users/'
2022-06-29 02:17:10 +03:00
/>
</div>
<SectionTabs activeTab='userlibraryaccess'/>
2021-10-16 00:26:31 +03:00
<form className='userLibraryAccessForm'>
<AccessContainer
2022-05-09 20:11:47 +03:00
containerClassName='folderAccessContainer'
headerTitle='HeaderLibraryAccess'
checkBoxClassName='chkEnableAllFolders'
checkBoxTitle='OptionEnableAccessToAllLibraries'
listContainerClassName='folderAccessListContainer'
accessClassName='folderAccess'
listTitle='HeaderLibraries'
description='LibraryAccessHelp'
>
{mediaFoldersItems.map(Item => (
2022-06-29 02:17:10 +03:00
<CheckBoxElement
key={Item.Id}
className='chkFolder'
2022-06-29 02:17:10 +03:00
itemId={Item.Id}
itemName={Item.Name}
itemCheckedAttribute={Item.checkedAttribute}
/>
))}
</AccessContainer>
<AccessContainer
2022-05-09 20:11:47 +03:00
containerClassName='channelAccessContainer hide'
headerTitle='HeaderChannelAccess'
checkBoxClassName='chkEnableAllChannels'
checkBoxTitle='OptionEnableAccessToAllChannels'
listContainerClassName='channelAccessListContainer'
accessClassName='channelAccess'
listTitle='Channels'
description='ChannelAccessHelp'
>
{channelsItems.map(Item => (
2022-06-29 02:17:10 +03:00
<CheckBoxElement
key={Item.Id}
className='chkChannel'
2022-06-29 02:17:10 +03:00
itemId={Item.Id}
itemName={Item.Name}
itemCheckedAttribute={Item.checkedAttribute}
/>
))}
</AccessContainer>
<AccessContainer
2022-05-09 20:11:47 +03:00
containerClassName='deviceAccessContainer hide'
headerTitle='HeaderDeviceAccess'
checkBoxClassName='chkEnableAllDevices'
checkBoxTitle='OptionEnableAccessFromAllDevices'
listContainerClassName='deviceAccessListContainer'
accessClassName='deviceAccess'
listTitle='HeaderDevices'
description='DeviceAccessHelp'
>
{devicesItems.map(Item => (
2022-06-29 02:17:10 +03:00
<CheckBoxElement
key={Item.Id}
className='chkDevice'
2022-06-29 02:17:10 +03:00
itemId={Item.Id}
2024-09-04 11:28:57 +03:00
itemName={Item.CustomName || Item.Name}
2022-06-29 02:17:10 +03:00
itemAppName={Item.AppName}
itemCheckedAttribute={Item.checkedAttribute}
/>
))}
</AccessContainer>
2021-10-16 00:26:31 +03:00
<br />
<div>
<ButtonElement
type='submit'
className='raised button-submit block'
title='Save'
/>
</div>
</form>
</div>
2022-06-29 03:21:45 +03:00
</Page>
2021-10-16 00:26:31 +03:00
);
};
2022-06-29 23:35:56 +03:00
export default UserLibraryAccess;