mirror of
https://github.com/jellyfin/jellyfin-web.git
synced 2026-01-15 16:33:35 -03:00
Merge pull request #7089 from viown/ts-sdk-user
Migrate dashboard user pages to use TS SDK
This commit is contained in:
21
src/apps/dashboard/features/users/api/useAuthProviders.ts
Normal file
21
src/apps/dashboard/features/users/api/useAuthProviders.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { Api } from '@jellyfin/sdk';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { useApi } from 'hooks/useApi';
|
||||||
|
import { getSessionApi } from '@jellyfin/sdk/lib/utils/api/session-api';
|
||||||
|
import type { AxiosRequestConfig } from 'axios';
|
||||||
|
|
||||||
|
const fetchAuthProviders = async (api: Api, options?: AxiosRequestConfig) => {
|
||||||
|
const response = await getSessionApi(api).getAuthProviders(options);
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useAuthProviders = () => {
|
||||||
|
const { api } = useApi();
|
||||||
|
|
||||||
|
return useQuery({
|
||||||
|
queryKey: [ 'AuthProviders' ],
|
||||||
|
queryFn: ({ signal }) => fetchAuthProviders(api!, { signal }),
|
||||||
|
enabled: !!api
|
||||||
|
});
|
||||||
|
};
|
||||||
22
src/apps/dashboard/features/users/api/useChannels.ts
Normal file
22
src/apps/dashboard/features/users/api/useChannels.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { Api } from '@jellyfin/sdk';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { useApi } from 'hooks/useApi';
|
||||||
|
import { getChannelsApi } from '@jellyfin/sdk/lib/utils/api/channels-api';
|
||||||
|
import { ChannelsApiGetChannelsRequest } from '@jellyfin/sdk/lib/generated-client/api/channels-api';
|
||||||
|
import type { AxiosRequestConfig } from 'axios';
|
||||||
|
|
||||||
|
const fetchChannels = async (api: Api, params?: ChannelsApiGetChannelsRequest, options?: AxiosRequestConfig) => {
|
||||||
|
const response = await getChannelsApi(api).getChannels(params, options);
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useChannels = (params?: ChannelsApiGetChannelsRequest) => {
|
||||||
|
const { api } = useApi();
|
||||||
|
|
||||||
|
return useQuery({
|
||||||
|
queryKey: [ 'Channels' ],
|
||||||
|
queryFn: ({ signal }) => fetchChannels(api!, params, { signal }),
|
||||||
|
enabled: !!api
|
||||||
|
});
|
||||||
|
};
|
||||||
15
src/apps/dashboard/features/users/api/useCreateUser.ts
Normal file
15
src/apps/dashboard/features/users/api/useCreateUser.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { UserApiCreateUserByNameRequest } from '@jellyfin/sdk/lib/generated-client/api/user-api';
|
||||||
|
import { getUserApi } from '@jellyfin/sdk/lib/utils/api/user-api';
|
||||||
|
import { useMutation } from '@tanstack/react-query';
|
||||||
|
import { useApi } from 'hooks/useApi';
|
||||||
|
|
||||||
|
export const useCreateUser = () => {
|
||||||
|
const { api } = useApi();
|
||||||
|
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: (params: UserApiCreateUserByNameRequest) => (
|
||||||
|
getUserApi(api!)
|
||||||
|
.createUserByName(params)
|
||||||
|
)
|
||||||
|
});
|
||||||
|
};
|
||||||
22
src/apps/dashboard/features/users/api/useDeleteUser.ts
Normal file
22
src/apps/dashboard/features/users/api/useDeleteUser.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { UserApiDeleteUserRequest } from '@jellyfin/sdk/lib/generated-client/api/user-api';
|
||||||
|
import { getUserApi } from '@jellyfin/sdk/lib/utils/api/user-api';
|
||||||
|
import { useMutation } from '@tanstack/react-query';
|
||||||
|
import { useApi } from 'hooks/useApi';
|
||||||
|
import { QUERY_KEY } from 'hooks/useUsers';
|
||||||
|
import { queryClient } from 'utils/query/queryClient';
|
||||||
|
|
||||||
|
export const useDeleteUser = () => {
|
||||||
|
const { api } = useApi();
|
||||||
|
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: (params: UserApiDeleteUserRequest) => (
|
||||||
|
getUserApi(api!)
|
||||||
|
.deleteUser(params)
|
||||||
|
),
|
||||||
|
onSuccess: () => {
|
||||||
|
void queryClient.invalidateQueries({
|
||||||
|
queryKey: [ QUERY_KEY ]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { Api } from '@jellyfin/sdk';
|
||||||
|
import { LibraryApiGetMediaFoldersRequest } from '@jellyfin/sdk/lib/generated-client/api/library-api';
|
||||||
|
import { getLibraryApi } from '@jellyfin/sdk/lib/utils/api/library-api';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { useApi } from 'hooks/useApi';
|
||||||
|
import type { AxiosRequestConfig } from 'axios';
|
||||||
|
|
||||||
|
const fetchLibraryMediaFolders = async (api: Api, params?: LibraryApiGetMediaFoldersRequest, options?: AxiosRequestConfig) => {
|
||||||
|
const response = await getLibraryApi(api).getMediaFolders(params, options);
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useLibraryMediaFolders = (params?: LibraryApiGetMediaFoldersRequest) => {
|
||||||
|
const { api } = useApi();
|
||||||
|
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['LibraryMediaFolders'],
|
||||||
|
queryFn: ({ signal }) => fetchLibraryMediaFolders(api!, params, { signal }),
|
||||||
|
enabled: !!api
|
||||||
|
});
|
||||||
|
};
|
||||||
22
src/apps/dashboard/features/users/api/useNetworkConfig.ts
Normal file
22
src/apps/dashboard/features/users/api/useNetworkConfig.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { Api } from '@jellyfin/sdk';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { useApi } from 'hooks/useApi';
|
||||||
|
import { getConfigurationApi } from '@jellyfin/sdk/lib/utils/api/configuration-api';
|
||||||
|
import type { AxiosRequestConfig } from 'axios';
|
||||||
|
import type { NetworkConfiguration } from '@jellyfin/sdk/lib/generated-client/models/network-configuration';
|
||||||
|
|
||||||
|
const fetchNetworkConfig = async (api: Api, options?: AxiosRequestConfig) => {
|
||||||
|
const response = await getConfigurationApi(api).getNamedConfiguration({ key: 'network' }, options);
|
||||||
|
|
||||||
|
return response.data as NetworkConfiguration;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useNetworkConfig = () => {
|
||||||
|
const { api } = useApi();
|
||||||
|
|
||||||
|
return useQuery({
|
||||||
|
queryKey: [ 'NetConfig' ],
|
||||||
|
queryFn: ({ signal }) => fetchNetworkConfig(api!, { signal }),
|
||||||
|
enabled: !!api
|
||||||
|
});
|
||||||
|
};
|
||||||
21
src/apps/dashboard/features/users/api/useParentalRatings.ts
Normal file
21
src/apps/dashboard/features/users/api/useParentalRatings.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { Api } from '@jellyfin/sdk';
|
||||||
|
import { getLocalizationApi } from '@jellyfin/sdk/lib/utils/api/localization-api';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { useApi } from 'hooks/useApi';
|
||||||
|
import type { AxiosRequestConfig } from 'axios';
|
||||||
|
|
||||||
|
const fetchParentalRatings = async (api: Api, options?: AxiosRequestConfig) => {
|
||||||
|
const response = await getLocalizationApi(api).getParentalRatings(options);
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useParentalRatings = () => {
|
||||||
|
const { api } = useApi();
|
||||||
|
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['ParentalRatings'],
|
||||||
|
queryFn: ({ signal }) => fetchParentalRatings(api!, { signal }),
|
||||||
|
enabled: !!api
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { Api } from '@jellyfin/sdk';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { useApi } from 'hooks/useApi';
|
||||||
|
import { getSessionApi } from '@jellyfin/sdk/lib/utils/api/session-api';
|
||||||
|
import type { AxiosRequestConfig } from 'axios';
|
||||||
|
|
||||||
|
const fetchPasswordResetProviders = async (api: Api, options?: AxiosRequestConfig) => {
|
||||||
|
const response = await getSessionApi(api).getPasswordResetProviders(options);
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const usePasswordResetProviders = () => {
|
||||||
|
const { api } = useApi();
|
||||||
|
|
||||||
|
return useQuery({
|
||||||
|
queryKey: [ 'PasswordResetProviders' ],
|
||||||
|
queryFn: ({ signal }) => fetchPasswordResetProviders(api!, { signal }),
|
||||||
|
enabled: !!api
|
||||||
|
});
|
||||||
|
};
|
||||||
22
src/apps/dashboard/features/users/api/useUpdateUser.ts
Normal file
22
src/apps/dashboard/features/users/api/useUpdateUser.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { UserApiUpdateUserRequest } from '@jellyfin/sdk/lib/generated-client/api/user-api';
|
||||||
|
import { getUserApi } from '@jellyfin/sdk/lib/utils/api/user-api';
|
||||||
|
import { useMutation } from '@tanstack/react-query';
|
||||||
|
import { useApi } from 'hooks/useApi';
|
||||||
|
import { queryClient } from 'utils/query/queryClient';
|
||||||
|
import { QUERY_KEY } from './useUser';
|
||||||
|
|
||||||
|
export const useUpdateUser = () => {
|
||||||
|
const { api } = useApi();
|
||||||
|
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: (params: UserApiUpdateUserRequest) => (
|
||||||
|
getUserApi(api!)
|
||||||
|
.updateUser(params)
|
||||||
|
),
|
||||||
|
onSuccess: (_, params) => {
|
||||||
|
void queryClient.invalidateQueries({
|
||||||
|
queryKey: [QUERY_KEY, params.userId]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
23
src/apps/dashboard/features/users/api/useUpdateUserPolicy.ts
Normal file
23
src/apps/dashboard/features/users/api/useUpdateUserPolicy.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { UserApiUpdateUserPolicyRequest } from '@jellyfin/sdk/lib/generated-client/api/user-api';
|
||||||
|
import { getUserApi } from '@jellyfin/sdk/lib/utils/api/user-api';
|
||||||
|
import { useMutation } from '@tanstack/react-query';
|
||||||
|
import { useApi } from 'hooks/useApi';
|
||||||
|
import { queryClient } from 'utils/query/queryClient';
|
||||||
|
import { QUERY_KEY } from './useUser';
|
||||||
|
|
||||||
|
export const useUpdateUserPolicy = () => {
|
||||||
|
const { api } = useApi();
|
||||||
|
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: (params: UserApiUpdateUserPolicyRequest) => (
|
||||||
|
|
||||||
|
getUserApi(api!)
|
||||||
|
.updateUserPolicy(params)
|
||||||
|
),
|
||||||
|
onSuccess: (_, params) => {
|
||||||
|
void queryClient.invalidateQueries({
|
||||||
|
queryKey: [QUERY_KEY, params.userId]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
24
src/apps/dashboard/features/users/api/useUser.ts
Normal file
24
src/apps/dashboard/features/users/api/useUser.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { Api } from '@jellyfin/sdk';
|
||||||
|
import { UserApiGetUserByIdRequest } from '@jellyfin/sdk/lib/generated-client/api/user-api';
|
||||||
|
import { getUserApi } from '@jellyfin/sdk/lib/utils/api/user-api';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { useApi } from 'hooks/useApi';
|
||||||
|
import type { AxiosRequestConfig } from 'axios';
|
||||||
|
|
||||||
|
export const QUERY_KEY = 'User';
|
||||||
|
|
||||||
|
const fetchUser = async (api: Api, params: UserApiGetUserByIdRequest, options?: AxiosRequestConfig) => {
|
||||||
|
const response = await getUserApi(api).getUserById(params, options);
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useUser = (params?: UserApiGetUserByIdRequest) => {
|
||||||
|
const { api } = useApi();
|
||||||
|
|
||||||
|
return useQuery({
|
||||||
|
queryKey: [ QUERY_KEY, params?.userId ],
|
||||||
|
queryFn: ({ signal }) => fetchUser(api!, params!, { signal }),
|
||||||
|
enabled: !!api && !!params
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client';
|
import type { BaseItemDto, CreateUserByName } from '@jellyfin/sdk/lib/generated-client';
|
||||||
import React, { useCallback, useEffect, useState, useRef } from 'react';
|
import React, { useCallback, useEffect, useState, useRef } from 'react';
|
||||||
|
|
||||||
import Dashboard from '../../../../utils/dashboard';
|
|
||||||
import globalize from '../../../../lib/globalize';
|
import globalize from '../../../../lib/globalize';
|
||||||
import loading from '../../../../components/loading/loading';
|
import loading from '../../../../components/loading/loading';
|
||||||
import SectionTitleContainer from '../../../../elements/SectionTitleContainer';
|
import SectionTitleContainer from '../../../../elements/SectionTitleContainer';
|
||||||
@@ -12,10 +11,11 @@ import CheckBoxElement from '../../../../elements/CheckBoxElement';
|
|||||||
import Page from '../../../../components/Page';
|
import Page from '../../../../components/Page';
|
||||||
import Toast from 'apps/dashboard/components/Toast';
|
import Toast from 'apps/dashboard/components/Toast';
|
||||||
|
|
||||||
type UserInput = {
|
import { useLibraryMediaFolders } from 'apps/dashboard/features/users/api/useLibraryMediaFolders';
|
||||||
Name?: string;
|
import { useChannels } from 'apps/dashboard/features/users/api/useChannels';
|
||||||
Password?: string;
|
import { useUpdateUserPolicy } from 'apps/dashboard/features/users/api/useUpdateUserPolicy';
|
||||||
};
|
import { useCreateUser } from 'apps/dashboard/features/users/api/useCreateUser';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
type ItemsArr = {
|
type ItemsArr = {
|
||||||
Name?: string | null;
|
Name?: string | null;
|
||||||
@@ -23,6 +23,7 @@ type ItemsArr = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const UserNew = () => {
|
const UserNew = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
const [ channelsItems, setChannelsItems ] = useState<ItemsArr[]>([]);
|
const [ channelsItems, setChannelsItems ] = useState<ItemsArr[]>([]);
|
||||||
const [ mediaFoldersItems, setMediaFoldersItems ] = useState<ItemsArr[]>([]);
|
const [ mediaFoldersItems, setMediaFoldersItems ] = useState<ItemsArr[]>([]);
|
||||||
const [ isErrorToastOpen, setIsErrorToastOpen ] = useState(false);
|
const [ isErrorToastOpen, setIsErrorToastOpen ] = useState(false);
|
||||||
@@ -31,6 +32,11 @@ const UserNew = () => {
|
|||||||
const handleToastClose = useCallback(() => {
|
const handleToastClose = useCallback(() => {
|
||||||
setIsErrorToastOpen(false);
|
setIsErrorToastOpen(false);
|
||||||
}, []);
|
}, []);
|
||||||
|
const { data: mediaFolders, isSuccess: isMediaFoldersSuccess } = useLibraryMediaFolders();
|
||||||
|
const { data: channels, isSuccess: isChannelsSuccess } = useChannels();
|
||||||
|
|
||||||
|
const createUser = useCreateUser();
|
||||||
|
const updateUserPolicy = useUpdateUserPolicy();
|
||||||
|
|
||||||
const getItemsResult = (items: BaseItemDto[]) => {
|
const getItemsResult = (items: BaseItemDto[]) => {
|
||||||
return items.map(item =>
|
return items.map(item =>
|
||||||
@@ -49,9 +55,7 @@ const UserNew = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const mediaFolders = getItemsResult(result);
|
setMediaFoldersItems(getItemsResult(result));
|
||||||
|
|
||||||
setMediaFoldersItems(mediaFolders);
|
|
||||||
|
|
||||||
const folderAccess = page.querySelector('.folderAccess') as HTMLDivElement;
|
const folderAccess = page.querySelector('.folderAccess') as HTMLDivElement;
|
||||||
folderAccess.dispatchEvent(new CustomEvent('create'));
|
folderAccess.dispatchEvent(new CustomEvent('create'));
|
||||||
@@ -67,15 +71,15 @@ const UserNew = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const channels = getItemsResult(result);
|
const channelItems = getItemsResult(result);
|
||||||
|
|
||||||
setChannelsItems(channels);
|
setChannelsItems(channelItems);
|
||||||
|
|
||||||
const channelAccess = page.querySelector('.channelAccess') as HTMLDivElement;
|
const channelAccess = page.querySelector('.channelAccess') as HTMLDivElement;
|
||||||
channelAccess.dispatchEvent(new CustomEvent('create'));
|
channelAccess.dispatchEvent(new CustomEvent('create'));
|
||||||
|
|
||||||
const channelAccessContainer = page.querySelector('.channelAccessContainer') as HTMLDivElement;
|
const channelAccessContainer = page.querySelector('.channelAccessContainer') as HTMLDivElement;
|
||||||
channels.length ? channelAccessContainer.classList.remove('hide') : channelAccessContainer.classList.add('hide');
|
channelItems.length ? channelAccessContainer.classList.remove('hide') : channelAccessContainer.classList.add('hide');
|
||||||
|
|
||||||
(page.querySelector('.chkEnableAllChannels') as HTMLInputElement).checked = false;
|
(page.querySelector('.chkEnableAllChannels') as HTMLInputElement).checked = false;
|
||||||
}, []);
|
}, []);
|
||||||
@@ -87,22 +91,26 @@ const UserNew = () => {
|
|||||||
console.error('Unexpected null reference');
|
console.error('Unexpected null reference');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!mediaFolders?.Items) {
|
||||||
|
console.error('[add] mediaFolders not available');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!channels?.Items) {
|
||||||
|
console.error('[add] channels not available');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
(page.querySelector('#txtUsername') as HTMLInputElement).value = '';
|
loadMediaFolders(mediaFolders?.Items);
|
||||||
(page.querySelector('#txtPassword') as HTMLInputElement).value = '';
|
loadChannels(channels?.Items);
|
||||||
|
loading.hide();
|
||||||
|
}, [loadChannels, loadMediaFolders, mediaFolders, channels]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
loading.show();
|
loading.show();
|
||||||
const promiseFolders = window.ApiClient.getJSON(window.ApiClient.getUrl('Library/MediaFolders', {
|
if (isMediaFoldersSuccess && isChannelsSuccess) {
|
||||||
IsHidden: false
|
loadUser();
|
||||||
}));
|
}
|
||||||
const promiseChannels = window.ApiClient.getJSON(window.ApiClient.getUrl('Channels'));
|
}, [loadUser, isMediaFoldersSuccess, isChannelsSuccess]);
|
||||||
Promise.all([promiseFolders, promiseChannels]).then(function (responses) {
|
|
||||||
loadMediaFolders(responses[0].Items);
|
|
||||||
loadChannels(responses[1].Items);
|
|
||||||
loading.hide();
|
|
||||||
}).catch(err => {
|
|
||||||
console.error('[usernew] failed to load data', err);
|
|
||||||
});
|
|
||||||
}, [loadChannels, loadMediaFolders]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const page = element.current;
|
const page = element.current;
|
||||||
@@ -112,51 +120,54 @@ const UserNew = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
loadUser();
|
|
||||||
|
|
||||||
const saveUser = () => {
|
const saveUser = () => {
|
||||||
const userInput: UserInput = {};
|
const userInput: CreateUserByName = {
|
||||||
userInput.Name = (page.querySelector('#txtUsername') as HTMLInputElement).value.trim();
|
Name: (page.querySelector('#txtUsername') as HTMLInputElement).value,
|
||||||
userInput.Password = (page.querySelector('#txtPassword') as HTMLInputElement).value;
|
Password: (page.querySelector('#txtPassword') as HTMLInputElement).value
|
||||||
|
};
|
||||||
|
createUser.mutate({ createUserByName: userInput }, {
|
||||||
|
onSuccess: (response) => {
|
||||||
|
const user = response.data;
|
||||||
|
|
||||||
window.ApiClient.createUser(userInput).then(function (user) {
|
if (!user.Id || !user.Policy) {
|
||||||
if (!user.Id || !user.Policy) {
|
throw new Error('Unexpected null user id or policy');
|
||||||
throw new Error('Unexpected null user id or policy');
|
}
|
||||||
}
|
|
||||||
|
|
||||||
user.Policy.EnableAllFolders = (page.querySelector('.chkEnableAllFolders') as HTMLInputElement).checked;
|
user.Policy.EnableAllFolders = (page.querySelector('.chkEnableAllFolders') as HTMLInputElement).checked;
|
||||||
user.Policy.EnabledFolders = [];
|
user.Policy.EnabledFolders = [];
|
||||||
|
|
||||||
if (!user.Policy.EnableAllFolders) {
|
if (!user.Policy.EnableAllFolders) {
|
||||||
user.Policy.EnabledFolders = Array.prototype.filter.call(page.querySelectorAll('.chkFolder'), function (i) {
|
user.Policy.EnabledFolders = Array.prototype.filter.call(page.querySelectorAll('.chkFolder'), function (i) {
|
||||||
return i.checked;
|
return i.checked;
|
||||||
}).map(function (i) {
|
}).map(function (i) {
|
||||||
return i.getAttribute('data-id');
|
return i.getAttribute('data-id');
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
user.Policy.EnableAllChannels = (page.querySelector('.chkEnableAllChannels') as HTMLInputElement).checked;
|
|
||||||
user.Policy.EnabledChannels = [];
|
|
||||||
|
|
||||||
if (!user.Policy.EnableAllChannels) {
|
|
||||||
user.Policy.EnabledChannels = Array.prototype.filter.call(page.querySelectorAll('.chkChannel'), function (i) {
|
|
||||||
return i.checked;
|
|
||||||
}).map(function (i) {
|
|
||||||
return i.getAttribute('data-id');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
window.ApiClient.updateUserPolicy(user.Id, user.Policy).then(function () {
|
|
||||||
Dashboard.navigate('/dashboard/users/profile?userId=' + user.Id)
|
|
||||||
.catch(err => {
|
|
||||||
console.error('[usernew] failed to navigate to edit user page', err);
|
|
||||||
});
|
});
|
||||||
}).catch(err => {
|
}
|
||||||
console.error('[usernew] failed to update user policy', err);
|
|
||||||
});
|
user.Policy.EnableAllChannels = (page.querySelector('.chkEnableAllChannels') as HTMLInputElement).checked;
|
||||||
}, function () {
|
user.Policy.EnabledChannels = [];
|
||||||
setIsErrorToastOpen(true);
|
|
||||||
loading.hide();
|
if (!user.Policy.EnableAllChannels) {
|
||||||
|
user.Policy.EnabledChannels = Array.prototype.filter.call(page.querySelectorAll('.chkChannel'), function (i) {
|
||||||
|
return i.checked;
|
||||||
|
}).map(function (i) {
|
||||||
|
return i.getAttribute('data-id');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateUserPolicy.mutate({
|
||||||
|
userId: user.Id,
|
||||||
|
userPolicy: user.Policy
|
||||||
|
}, {
|
||||||
|
onSuccess: () => {
|
||||||
|
navigate(`/dashboard/users/profile?userId=${user.Id}`);
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
console.error('[usernew] failed to update user policy');
|
||||||
|
setIsErrorToastOpen(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -168,22 +179,32 @@ const UserNew = () => {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
(page.querySelector('.chkEnableAllChannels') as HTMLInputElement).addEventListener('change', function (this: HTMLInputElement) {
|
const enableAllChannelsChange = function (this: HTMLInputElement) {
|
||||||
const channelAccessListContainer = page.querySelector('.channelAccessListContainer') as HTMLDivElement;
|
const channelAccessListContainer = page.querySelector('.channelAccessListContainer') as HTMLDivElement;
|
||||||
this.checked ? channelAccessListContainer.classList.add('hide') : channelAccessListContainer.classList.remove('hide');
|
this.checked ? channelAccessListContainer.classList.add('hide') : channelAccessListContainer.classList.remove('hide');
|
||||||
});
|
};
|
||||||
|
|
||||||
(page.querySelector('.chkEnableAllFolders') as HTMLInputElement).addEventListener('change', function (this: HTMLInputElement) {
|
const enableAllFoldersChange = function (this: HTMLInputElement) {
|
||||||
const folderAccessListContainer = page.querySelector('.folderAccessListContainer') as HTMLDivElement;
|
const folderAccessListContainer = page.querySelector('.folderAccessListContainer') as HTMLDivElement;
|
||||||
this.checked ? folderAccessListContainer.classList.add('hide') : folderAccessListContainer.classList.remove('hide');
|
this.checked ? folderAccessListContainer.classList.add('hide') : folderAccessListContainer.classList.remove('hide');
|
||||||
});
|
};
|
||||||
|
|
||||||
(page.querySelector('.newUserProfileForm') as HTMLFormElement).addEventListener('submit', onSubmit);
|
const onCancelClick = () => {
|
||||||
|
|
||||||
(page.querySelector('#btnCancel') as HTMLButtonElement).addEventListener('click', function() {
|
|
||||||
window.history.back();
|
window.history.back();
|
||||||
});
|
};
|
||||||
}, [loadUser]);
|
|
||||||
|
(page.querySelector('.chkEnableAllChannels') as HTMLInputElement).addEventListener('change', enableAllChannelsChange);
|
||||||
|
(page.querySelector('.chkEnableAllFolders') as HTMLInputElement).addEventListener('change', enableAllFoldersChange);
|
||||||
|
(page.querySelector('.newUserProfileForm') as HTMLFormElement).addEventListener('submit', onSubmit);
|
||||||
|
(page.querySelector('#btnCancel') as HTMLButtonElement).addEventListener('click', onCancelClick);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
(page.querySelector('.chkEnableAllChannels') as HTMLInputElement).removeEventListener('change', enableAllChannelsChange);
|
||||||
|
(page.querySelector('.chkEnableAllFolders') as HTMLInputElement).removeEventListener('change', enableAllFoldersChange);
|
||||||
|
(page.querySelector('.newUserProfileForm') as HTMLFormElement).removeEventListener('submit', onSubmit);
|
||||||
|
(page.querySelector('#btnCancel') as HTMLButtonElement).removeEventListener('click', onCancelClick);
|
||||||
|
};
|
||||||
|
}, [loadUser, createUser, updateUserPolicy, navigate]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page
|
<Page
|
||||||
|
|||||||
@@ -1,10 +1,5 @@
|
|||||||
import type { UserDto } from '@jellyfin/sdk/lib/generated-client';
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import React, { useEffect, useState, useRef, useCallback } from 'react';
|
|
||||||
|
|
||||||
import Dashboard from '../../../../utils/dashboard';
|
|
||||||
import globalize from '../../../../lib/globalize';
|
import globalize from '../../../../lib/globalize';
|
||||||
import loading from '../../../../components/loading/loading';
|
|
||||||
import dom from '../../../../utils/dom';
|
|
||||||
import confirm from '../../../../components/confirm/confirm';
|
import confirm from '../../../../components/confirm/confirm';
|
||||||
import UserCardBox from '../../../../components/dashboard/users/UserCardBox';
|
import UserCardBox from '../../../../components/dashboard/users/UserCardBox';
|
||||||
import SectionTitleContainer from '../../../../elements/SectionTitleContainer';
|
import SectionTitleContainer from '../../../../elements/SectionTitleContainer';
|
||||||
@@ -14,8 +9,12 @@ import '../../../../components/cardbuilder/card.scss';
|
|||||||
import '../../../../components/indicators/indicators.scss';
|
import '../../../../components/indicators/indicators.scss';
|
||||||
import '../../../../styles/flexstyles.scss';
|
import '../../../../styles/flexstyles.scss';
|
||||||
import Page from '../../../../components/Page';
|
import Page from '../../../../components/Page';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation, useNavigate } from 'react-router-dom';
|
||||||
import Toast from 'apps/dashboard/components/Toast';
|
import Toast from 'apps/dashboard/components/Toast';
|
||||||
|
import { useUsers } from 'hooks/useUsers';
|
||||||
|
import Loading from 'components/loading/LoadingComponent';
|
||||||
|
import { useDeleteUser } from 'apps/dashboard/features/users/api/useDeleteUser';
|
||||||
|
import dom from 'utils/dom';
|
||||||
|
|
||||||
type MenuEntry = {
|
type MenuEntry = {
|
||||||
name?: string;
|
name?: string;
|
||||||
@@ -26,24 +25,15 @@ type MenuEntry = {
|
|||||||
const UserProfiles = () => {
|
const UserProfiles = () => {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const [ isSettingsSavedToastOpen, setIsSettingsSavedToastOpen ] = useState(false);
|
const [ isSettingsSavedToastOpen, setIsSettingsSavedToastOpen ] = useState(false);
|
||||||
const [ users, setUsers ] = useState<UserDto[]>([]);
|
|
||||||
|
|
||||||
const element = useRef<HTMLDivElement>(null);
|
const element = useRef<HTMLDivElement>(null);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { data: users, isPending } = useUsers();
|
||||||
|
const deleteUser = useDeleteUser();
|
||||||
|
|
||||||
const handleToastClose = useCallback(() => {
|
const handleToastClose = useCallback(() => {
|
||||||
setIsSettingsSavedToastOpen(false);
|
setIsSettingsSavedToastOpen(false);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const loadData = () => {
|
|
||||||
loading.show();
|
|
||||||
window.ApiClient.getUsers().then(function (result) {
|
|
||||||
setUsers(result);
|
|
||||||
loading.hide();
|
|
||||||
}).catch(err => {
|
|
||||||
console.error('[userprofiles] failed to fetch users', err);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const page = element.current;
|
const page = element.current;
|
||||||
|
|
||||||
@@ -57,8 +47,6 @@ const UserProfiles = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
loadData();
|
|
||||||
|
|
||||||
const showUserMenu = (elem: HTMLElement) => {
|
const showUserMenu = (elem: HTMLElement) => {
|
||||||
const card = dom.parentWithClass(elem, 'card');
|
const card = dom.parentWithClass(elem, 'card');
|
||||||
const userId = card?.getAttribute('data-userid');
|
const userId = card?.getAttribute('data-userid');
|
||||||
@@ -99,28 +87,19 @@ const UserProfiles = () => {
|
|||||||
callback: function (id: string) {
|
callback: function (id: string) {
|
||||||
switch (id) {
|
switch (id) {
|
||||||
case 'open':
|
case 'open':
|
||||||
Dashboard.navigate('/dashboard/users/profile?userId=' + userId)
|
navigate(`/dashboard/users/profile?userId=${userId}`);
|
||||||
.catch(err => {
|
|
||||||
console.error('[userprofiles] failed to navigate to user edit page', err);
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'access':
|
case 'access':
|
||||||
Dashboard.navigate('/dashboard/users/access?userId=' + userId)
|
navigate(`/dashboard/users/access?userId=${userId}`);
|
||||||
.catch(err => {
|
|
||||||
console.error('[userprofiles] failed to navigate to user library page', err);
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'parentalcontrol':
|
case 'parentalcontrol':
|
||||||
Dashboard.navigate('/dashboard/users/parentalcontrol?userId=' + userId)
|
navigate(`/dashboard/users/parentalcontrol?userId=${userId}`);
|
||||||
.catch(err => {
|
|
||||||
console.error('[userprofiles] failed to navigate to parental control page', err);
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'delete':
|
case 'delete':
|
||||||
deleteUser(userId, username);
|
confirmDeleteUser(userId, username);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
@@ -131,7 +110,7 @@ const UserProfiles = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteUser = (id: string, username?: string | null) => {
|
const confirmDeleteUser = (id: string, username?: string | null) => {
|
||||||
const title = username ? globalize.translate('DeleteName', username) : globalize.translate('DeleteUser');
|
const title = username ? globalize.translate('DeleteName', username) : globalize.translate('DeleteUser');
|
||||||
const text = globalize.translate('DeleteUserConfirmation');
|
const text = globalize.translate('DeleteUserConfirmation');
|
||||||
|
|
||||||
@@ -141,32 +120,38 @@ const UserProfiles = () => {
|
|||||||
confirmText: globalize.translate('Delete'),
|
confirmText: globalize.translate('Delete'),
|
||||||
primary: 'delete'
|
primary: 'delete'
|
||||||
}).then(function () {
|
}).then(function () {
|
||||||
loading.show();
|
deleteUser.mutate({
|
||||||
window.ApiClient.deleteUser(id).then(function () {
|
userId: id
|
||||||
loadData();
|
|
||||||
}).catch(err => {
|
|
||||||
console.error('[userprofiles] failed to delete user', err);
|
|
||||||
});
|
});
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
// confirm dialog closed
|
// confirm dialog closed
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
page.addEventListener('click', function (e) {
|
const onPageClick = function (e: MouseEvent) {
|
||||||
const btnUserMenu = dom.parentWithClass(e.target as HTMLElement, 'btnUserMenu');
|
const btnUserMenu = dom.parentWithClass(e.target as HTMLElement, 'btnUserMenu');
|
||||||
|
|
||||||
if (btnUserMenu) {
|
if (btnUserMenu) {
|
||||||
showUserMenu(btnUserMenu);
|
showUserMenu(btnUserMenu);
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
(page.querySelector('#btnAddUser') as HTMLButtonElement).addEventListener('click', function() {
|
const onAddUserClick = function() {
|
||||||
Dashboard.navigate('/dashboard/users/add')
|
navigate('/dashboard/users/add');
|
||||||
.catch(err => {
|
};
|
||||||
console.error('[userprofiles] failed to navigate to new user page', err);
|
|
||||||
});
|
page.addEventListener('click', onPageClick);
|
||||||
});
|
(page.querySelector('#btnAddUser') as HTMLButtonElement).addEventListener('click', onAddUserClick);
|
||||||
}, []);
|
|
||||||
|
return () => {
|
||||||
|
page.removeEventListener('click', onPageClick);
|
||||||
|
(page.querySelector('#btnAddUser') as HTMLButtonElement).removeEventListener('click', onAddUserClick);
|
||||||
|
};
|
||||||
|
}, [navigate, deleteUser, location.state?.openSavedToast]);
|
||||||
|
|
||||||
|
if (isPending) {
|
||||||
|
return <Loading />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page
|
<Page
|
||||||
@@ -192,7 +177,7 @@ const UserProfiles = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='localUsers itemsContainer vertical-wrap'>
|
<div className='localUsers itemsContainer vertical-wrap'>
|
||||||
{users.map(user => {
|
{users?.map(user => {
|
||||||
return <UserCardBox key={user.Id} user={user} />;
|
return <UserCardBox key={user.Id} user={user} />;
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,37 +1,21 @@
|
|||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React from 'react';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
|
|
||||||
import SectionTabs from '../../../../components/dashboard/users/SectionTabs';
|
import SectionTabs from '../../../../components/dashboard/users/SectionTabs';
|
||||||
import UserPasswordForm from '../../../../components/dashboard/users/UserPasswordForm';
|
import UserPasswordForm from '../../../../components/dashboard/users/UserPasswordForm';
|
||||||
import SectionTitleContainer from '../../../../elements/SectionTitleContainer';
|
import SectionTitleContainer from '../../../../elements/SectionTitleContainer';
|
||||||
import Page from '../../../../components/Page';
|
import Page from '../../../../components/Page';
|
||||||
import loading from '../../../../components/loading/loading';
|
import { useUser } from 'apps/dashboard/features/users/api/useUser';
|
||||||
|
import Loading from 'components/loading/LoadingComponent';
|
||||||
|
|
||||||
const UserPassword = () => {
|
const UserPassword = () => {
|
||||||
const [ searchParams ] = useSearchParams();
|
const [ searchParams ] = useSearchParams();
|
||||||
const userId = searchParams.get('userId');
|
const userId = searchParams.get('userId');
|
||||||
const [ userName, setUserName ] = useState('');
|
const { data: user, isPending } = useUser(userId ? { userId: userId } : undefined);
|
||||||
|
|
||||||
const loadUser = useCallback(() => {
|
if (isPending || !user) {
|
||||||
if (!userId) {
|
return <Loading />;
|
||||||
console.error('[userpassword] missing user id');
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
loading.show();
|
|
||||||
window.ApiClient.getUser(userId).then(function (user) {
|
|
||||||
if (!user.Name) {
|
|
||||||
throw new Error('Unexpected null user.Name');
|
|
||||||
}
|
|
||||||
setUserName(user.Name);
|
|
||||||
loading.hide();
|
|
||||||
}).catch(err => {
|
|
||||||
console.error('[userpassword] failed to fetch user', err);
|
|
||||||
});
|
|
||||||
}, [userId]);
|
|
||||||
useEffect(() => {
|
|
||||||
loadUser();
|
|
||||||
}, [loadUser]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page
|
<Page
|
||||||
@@ -41,13 +25,13 @@ const UserPassword = () => {
|
|||||||
<div className='content-primary'>
|
<div className='content-primary'>
|
||||||
<div className='verticalSection'>
|
<div className='verticalSection'>
|
||||||
<SectionTitleContainer
|
<SectionTitleContainer
|
||||||
title={userName}
|
title={user?.Name || undefined}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<SectionTabs activeTab='userpassword'/>
|
<SectionTabs activeTab='userpassword'/>
|
||||||
<div className='readOnlyContent'>
|
<div className='readOnlyContent'>
|
||||||
<UserPasswordForm
|
<UserPasswordForm
|
||||||
userId={userId}
|
user={user}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -13,6 +13,14 @@ import SectionTabs from '../../../../components/dashboard/users/SectionTabs';
|
|||||||
import loading from '../../../../components/loading/loading';
|
import loading from '../../../../components/loading/loading';
|
||||||
import SelectElement from '../../../../elements/SelectElement';
|
import SelectElement from '../../../../elements/SelectElement';
|
||||||
import Page from '../../../../components/Page';
|
import Page from '../../../../components/Page';
|
||||||
|
import { useUser } from 'apps/dashboard/features/users/api/useUser';
|
||||||
|
import { useAuthProviders } from 'apps/dashboard/features/users/api/useAuthProviders';
|
||||||
|
import { usePasswordResetProviders } from 'apps/dashboard/features/users/api/usePasswordResetProviders';
|
||||||
|
import { useLibraryMediaFolders } from 'apps/dashboard/features/users/api/useLibraryMediaFolders';
|
||||||
|
import { useChannels } from 'apps/dashboard/features/users/api/useChannels';
|
||||||
|
import { useUpdateUser } from 'apps/dashboard/features/users/api/useUpdateUser';
|
||||||
|
import { useUpdateUserPolicy } from 'apps/dashboard/features/users/api/useUpdateUserPolicy';
|
||||||
|
import { useNetworkConfig } from 'apps/dashboard/features/users/api/useNetworkConfig';
|
||||||
|
|
||||||
type ResetProvider = BaseItemDto & {
|
type ResetProvider = BaseItemDto & {
|
||||||
checkedAttribute: string
|
checkedAttribute: string
|
||||||
@@ -27,15 +35,22 @@ const UserEdit = () => {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [ searchParams ] = useSearchParams();
|
const [ searchParams ] = useSearchParams();
|
||||||
const userId = searchParams.get('userId');
|
const userId = searchParams.get('userId');
|
||||||
const [ userDto, setUserDto ] = useState<UserDto>();
|
|
||||||
const [ deleteFoldersAccess, setDeleteFoldersAccess ] = useState<ResetProvider[]>([]);
|
const [ deleteFoldersAccess, setDeleteFoldersAccess ] = useState<ResetProvider[]>([]);
|
||||||
const [ authProviders, setAuthProviders ] = useState<NameIdPair[]>([]);
|
|
||||||
const [ passwordResetProviders, setPasswordResetProviders ] = useState<NameIdPair[]>([]);
|
|
||||||
const libraryMenu = useMemo(async () => ((await import('../../../../scripts/libraryMenu')).default), []);
|
const libraryMenu = useMemo(async () => ((await import('../../../../scripts/libraryMenu')).default), []);
|
||||||
|
|
||||||
const [ authenticationProviderId, setAuthenticationProviderId ] = useState('');
|
const [ authenticationProviderId, setAuthenticationProviderId ] = useState('');
|
||||||
const [ passwordResetProviderId, setPasswordResetProviderId ] = useState('');
|
const [ passwordResetProviderId, setPasswordResetProviderId ] = useState('');
|
||||||
|
|
||||||
|
const { data: userDto, isSuccess: isUserSuccess } = useUser(userId ? { userId: userId } : undefined);
|
||||||
|
const { data: authProviders, isSuccess: isAuthProvidersSuccess } = useAuthProviders();
|
||||||
|
const { data: passwordResetProviders, isSuccess: isPasswordResetProvidersSuccess } = usePasswordResetProviders();
|
||||||
|
const { data: mediaFolders, isSuccess: isMediaFoldersSuccess } = useLibraryMediaFolders({ isHidden: false });
|
||||||
|
const { data: channels, isSuccess: isChannelsSuccess } = useChannels({ supportsMediaDeletion: true });
|
||||||
|
const { data: netConfig, isSuccess: isNetConfigSuccess } = useNetworkConfig();
|
||||||
|
|
||||||
|
const updateUser = useUpdateUser();
|
||||||
|
const updateUserPolicy = useUpdateUserPolicy();
|
||||||
|
|
||||||
const element = useRef<HTMLDivElement>(null);
|
const element = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const triggerChange = (select: HTMLInputElement) => {
|
const triggerChange = (select: HTMLInputElement) => {
|
||||||
@@ -43,17 +58,10 @@ const UserEdit = () => {
|
|||||||
select.dispatchEvent(evt);
|
select.dispatchEvent(evt);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getUser = () => {
|
|
||||||
if (!userId) throw new Error('missing user id');
|
|
||||||
return window.ApiClient.getUser(userId);
|
|
||||||
};
|
|
||||||
|
|
||||||
const loadAuthProviders = useCallback((page: HTMLDivElement, user: UserDto, providers: NameIdPair[]) => {
|
const loadAuthProviders = useCallback((page: HTMLDivElement, user: UserDto, providers: NameIdPair[]) => {
|
||||||
const fldSelectLoginProvider = page.querySelector('.fldSelectLoginProvider') as HTMLDivElement;
|
const fldSelectLoginProvider = page.querySelector('.fldSelectLoginProvider') as HTMLDivElement;
|
||||||
fldSelectLoginProvider.classList.toggle('hide', providers.length <= 1);
|
fldSelectLoginProvider.classList.toggle('hide', providers.length <= 1);
|
||||||
|
|
||||||
setAuthProviders(providers);
|
|
||||||
|
|
||||||
const currentProviderId = user.Policy?.AuthenticationProviderId || '';
|
const currentProviderId = user.Policy?.AuthenticationProviderId || '';
|
||||||
setAuthenticationProviderId(currentProviderId);
|
setAuthenticationProviderId(currentProviderId);
|
||||||
}, []);
|
}, []);
|
||||||
@@ -62,30 +70,26 @@ const UserEdit = () => {
|
|||||||
const fldSelectPasswordResetProvider = page.querySelector('.fldSelectPasswordResetProvider') as HTMLDivElement;
|
const fldSelectPasswordResetProvider = page.querySelector('.fldSelectPasswordResetProvider') as HTMLDivElement;
|
||||||
fldSelectPasswordResetProvider.classList.toggle('hide', providers.length <= 1);
|
fldSelectPasswordResetProvider.classList.toggle('hide', providers.length <= 1);
|
||||||
|
|
||||||
setPasswordResetProviders(providers);
|
|
||||||
|
|
||||||
const currentProviderId = user.Policy?.PasswordResetProviderId || '';
|
const currentProviderId = user.Policy?.PasswordResetProviderId || '';
|
||||||
setPasswordResetProviderId(currentProviderId);
|
setPasswordResetProviderId(currentProviderId);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const loadDeleteFolders = useCallback((page: HTMLDivElement, user: UserDto, mediaFolders: BaseItemDto[]) => {
|
const loadDeleteFolders = useCallback((page: HTMLDivElement, user: UserDto, folders: BaseItemDto[]) => {
|
||||||
window.ApiClient.getJSON(window.ApiClient.getUrl('Channels', {
|
let isChecked;
|
||||||
SupportsMediaDeletion: true
|
let checkedAttribute;
|
||||||
})).then(function (channelsResult) {
|
const itemsArr: ResetProvider[] = [];
|
||||||
let isChecked;
|
|
||||||
let checkedAttribute;
|
|
||||||
const itemsArr: ResetProvider[] = [];
|
|
||||||
|
|
||||||
for (const mediaFolder of mediaFolders) {
|
for (const mediaFolder of folders) {
|
||||||
isChecked = user.Policy?.EnableContentDeletion || user.Policy?.EnableContentDeletionFromFolders?.indexOf(mediaFolder.Id || '') != -1;
|
isChecked = user.Policy?.EnableContentDeletion || user.Policy?.EnableContentDeletionFromFolders?.indexOf(mediaFolder.Id || '') != -1;
|
||||||
checkedAttribute = isChecked ? ' checked="checked"' : '';
|
checkedAttribute = isChecked ? ' checked="checked"' : '';
|
||||||
itemsArr.push({
|
itemsArr.push({
|
||||||
...mediaFolder,
|
...mediaFolder,
|
||||||
checkedAttribute: checkedAttribute
|
checkedAttribute: checkedAttribute
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const channel of channelsResult.Items) {
|
if (channels?.Items) {
|
||||||
|
for (const channel of channels.Items) {
|
||||||
isChecked = user.Policy?.EnableContentDeletion || user.Policy?.EnableContentDeletionFromFolders?.indexOf(channel.Id || '') != -1;
|
isChecked = user.Policy?.EnableContentDeletion || user.Policy?.EnableContentDeletionFromFolders?.indexOf(channel.Id || '') != -1;
|
||||||
checkedAttribute = isChecked ? ' checked="checked"' : '';
|
checkedAttribute = isChecked ? ' checked="checked"' : '';
|
||||||
itemsArr.push({
|
itemsArr.push({
|
||||||
@@ -93,16 +97,66 @@ const UserEdit = () => {
|
|||||||
checkedAttribute: checkedAttribute
|
checkedAttribute: checkedAttribute
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setDeleteFoldersAccess(itemsArr);
|
setDeleteFoldersAccess(itemsArr);
|
||||||
|
|
||||||
const chkEnableDeleteAllFolders = page.querySelector('.chkEnableDeleteAllFolders') as HTMLInputElement;
|
const chkEnableDeleteAllFolders = page.querySelector('.chkEnableDeleteAllFolders') as HTMLInputElement;
|
||||||
chkEnableDeleteAllFolders.checked = user.Policy?.EnableContentDeletion || false;
|
chkEnableDeleteAllFolders.checked = user.Policy?.EnableContentDeletion || false;
|
||||||
triggerChange(chkEnableDeleteAllFolders);
|
triggerChange(chkEnableDeleteAllFolders);
|
||||||
}).catch(err => {
|
}, [channels]);
|
||||||
console.error('[useredit] failed to fetch channels', err);
|
|
||||||
});
|
useEffect(() => {
|
||||||
}, []);
|
const page = element.current;
|
||||||
|
|
||||||
|
if (!page) {
|
||||||
|
console.error('[useredit] Unexpected null page reference');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userDto && isAuthProvidersSuccess && authProviders != null) {
|
||||||
|
loadAuthProviders(page, userDto, authProviders);
|
||||||
|
}
|
||||||
|
}, [authProviders, isAuthProvidersSuccess, userDto, loadAuthProviders]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const page = element.current;
|
||||||
|
|
||||||
|
if (!page) {
|
||||||
|
console.error('[useredit] Unexpected null page reference');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userDto && isPasswordResetProvidersSuccess && passwordResetProviders != null) {
|
||||||
|
loadPasswordResetProviders(page, userDto, passwordResetProviders);
|
||||||
|
}
|
||||||
|
}, [passwordResetProviders, isPasswordResetProvidersSuccess, userDto, loadPasswordResetProviders]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const page = element.current;
|
||||||
|
|
||||||
|
if (!page) {
|
||||||
|
console.error('[useredit] Unexpected null page reference');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userDto && isMediaFoldersSuccess && isChannelsSuccess && mediaFolders?.Items != null) {
|
||||||
|
loadDeleteFolders(page, userDto, mediaFolders.Items);
|
||||||
|
}
|
||||||
|
}, [userDto, mediaFolders, isMediaFoldersSuccess, isChannelsSuccess, channels, loadDeleteFolders]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const page = element.current;
|
||||||
|
|
||||||
|
if (!page) {
|
||||||
|
console.error('[useredit] Unexpected null page reference');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (netConfig && isNetConfigSuccess) {
|
||||||
|
(page.querySelector('.fldRemoteAccess') as HTMLDivElement).classList.toggle('hide', !netConfig.EnableRemoteAccess);
|
||||||
|
}
|
||||||
|
}, [netConfig, isNetConfigSuccess]);
|
||||||
|
|
||||||
const loadUser = useCallback((user: UserDto) => {
|
const loadUser = useCallback((user: UserDto) => {
|
||||||
const page = element.current;
|
const page = element.current;
|
||||||
@@ -112,24 +166,6 @@ const UserEdit = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.ApiClient.getJSON(window.ApiClient.getUrl('Auth/Providers')).then(function (providers) {
|
|
||||||
loadAuthProviders(page, user, providers);
|
|
||||||
}).catch(err => {
|
|
||||||
console.error('[useredit] failed to fetch auth providers', err);
|
|
||||||
});
|
|
||||||
window.ApiClient.getJSON(window.ApiClient.getUrl('Auth/PasswordResetProviders')).then(function (providers) {
|
|
||||||
loadPasswordResetProviders(page, user, providers);
|
|
||||||
}).catch(err => {
|
|
||||||
console.error('[useredit] failed to fetch password reset providers', err);
|
|
||||||
});
|
|
||||||
window.ApiClient.getJSON(window.ApiClient.getUrl('Library/MediaFolders', {
|
|
||||||
IsHidden: false
|
|
||||||
})).then(function (folders) {
|
|
||||||
loadDeleteFolders(page, user, folders.Items);
|
|
||||||
}).catch(err => {
|
|
||||||
console.error('[useredit] failed to fetch media folders', err);
|
|
||||||
});
|
|
||||||
|
|
||||||
const disabledUserBanner = page.querySelector('.disabledUserBanner') as HTMLDivElement;
|
const disabledUserBanner = page.querySelector('.disabledUserBanner') as HTMLDivElement;
|
||||||
disabledUserBanner.classList.toggle('hide', !user.Policy?.IsDisabled);
|
disabledUserBanner.classList.toggle('hide', !user.Policy?.IsDisabled);
|
||||||
|
|
||||||
@@ -139,7 +175,6 @@ const UserEdit = () => {
|
|||||||
|
|
||||||
void libraryMenu.then(menu => menu.setTitle(user.Name));
|
void libraryMenu.then(menu => menu.setTitle(user.Name));
|
||||||
|
|
||||||
setUserDto(user);
|
|
||||||
(page.querySelector('#txtUserName') as HTMLInputElement).value = user.Name || '';
|
(page.querySelector('#txtUserName') as HTMLInputElement).value = user.Name || '';
|
||||||
(page.querySelector('.chkIsAdmin') as HTMLInputElement).checked = !!user.Policy?.IsAdministrator;
|
(page.querySelector('.chkIsAdmin') as HTMLInputElement).checked = !!user.Policy?.IsAdministrator;
|
||||||
(page.querySelector('.chkDisabled') as HTMLInputElement).checked = !!user.Policy?.IsDisabled;
|
(page.querySelector('.chkDisabled') as HTMLInputElement).checked = !!user.Policy?.IsDisabled;
|
||||||
@@ -163,16 +198,22 @@ const UserEdit = () => {
|
|||||||
(page.querySelector('#txtMaxActiveSessions') as HTMLInputElement).value = String(user.Policy?.MaxActiveSessions) || '0';
|
(page.querySelector('#txtMaxActiveSessions') as HTMLInputElement).value = String(user.Policy?.MaxActiveSessions) || '0';
|
||||||
(page.querySelector('#selectSyncPlayAccess') as HTMLSelectElement).value = String(user.Policy?.SyncPlayAccess);
|
(page.querySelector('#selectSyncPlayAccess') as HTMLSelectElement).value = String(user.Policy?.SyncPlayAccess);
|
||||||
loading.hide();
|
loading.hide();
|
||||||
}, [loadAuthProviders, loadPasswordResetProviders, loadDeleteFolders ]);
|
}, [ libraryMenu ]);
|
||||||
|
|
||||||
const loadData = useCallback(() => {
|
const loadData = useCallback(() => {
|
||||||
|
if (!userDto) {
|
||||||
|
console.error('[profile] No user available');
|
||||||
|
return;
|
||||||
|
}
|
||||||
loading.show();
|
loading.show();
|
||||||
getUser().then(function (user) {
|
loadUser(userDto);
|
||||||
loadUser(user);
|
}, [userDto, loadUser]);
|
||||||
}).catch(err => {
|
|
||||||
console.error('[useredit] failed to load data', err);
|
useEffect(() => {
|
||||||
});
|
if (isUserSuccess) {
|
||||||
}, [loadUser]);
|
loadData();
|
||||||
|
}
|
||||||
|
}, [loadData, isUserSuccess]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const page = element.current;
|
const page = element.current;
|
||||||
@@ -182,8 +223,6 @@ const UserEdit = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
loadData();
|
|
||||||
|
|
||||||
const saveUser = (user: UserDto) => {
|
const saveUser = (user: UserDto) => {
|
||||||
if (!user.Id || !user.Policy) {
|
if (!user.Id || !user.Policy) {
|
||||||
throw new Error('Unexpected null user id or policy');
|
throw new Error('Unexpected null user id or policy');
|
||||||
@@ -215,53 +254,58 @@ const UserEdit = () => {
|
|||||||
user.Policy.EnableContentDeletionFromFolders = user.Policy.EnableContentDeletion ? [] : getCheckedElementDataIds(page.querySelectorAll('.chkFolder'));
|
user.Policy.EnableContentDeletionFromFolders = user.Policy.EnableContentDeletion ? [] : getCheckedElementDataIds(page.querySelectorAll('.chkFolder'));
|
||||||
user.Policy.SyncPlayAccess = (page.querySelector('#selectSyncPlayAccess') as HTMLSelectElement).value as SyncPlayUserAccessType;
|
user.Policy.SyncPlayAccess = (page.querySelector('#selectSyncPlayAccess') as HTMLSelectElement).value as SyncPlayUserAccessType;
|
||||||
|
|
||||||
window.ApiClient.updateUser(user).then(() => (
|
updateUser.mutate({ userId: user.Id, userDto: user }, {
|
||||||
window.ApiClient.updateUserPolicy(user.Id || '', user.Policy || { PasswordResetProviderId: '', AuthenticationProviderId: '' })
|
onSuccess: () => {
|
||||||
)).then(() => {
|
if (user.Id) {
|
||||||
navigate('/dashboard/users', {
|
updateUserPolicy.mutate({
|
||||||
state: { openSavedToast: true }
|
userId: user.Id,
|
||||||
});
|
userPolicy: user.Policy || { PasswordResetProviderId: '', AuthenticationProviderId: '' }
|
||||||
loading.hide();
|
}, {
|
||||||
}).catch(err => {
|
onSuccess: () => {
|
||||||
console.error('[useredit] failed to update user', err);
|
loading.hide();
|
||||||
|
navigate('/dashboard/users', {
|
||||||
|
state: { openSavedToast: true }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSubmit = (e: Event) => {
|
const onSubmit = (e: Event) => {
|
||||||
loading.show();
|
loading.show();
|
||||||
getUser().then(function (result) {
|
if (userDto) {
|
||||||
saveUser(result);
|
saveUser(userDto);
|
||||||
}).catch(err => {
|
}
|
||||||
console.error('[useredit] failed to fetch user', err);
|
|
||||||
});
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onBtnCancelClick = () => {
|
||||||
|
window.history.back();
|
||||||
|
};
|
||||||
|
|
||||||
(page.querySelector('.chkEnableDeleteAllFolders') as HTMLInputElement).addEventListener('change', function (this: HTMLInputElement) {
|
(page.querySelector('.chkEnableDeleteAllFolders') as HTMLInputElement).addEventListener('change', function (this: HTMLInputElement) {
|
||||||
(page.querySelector('.deleteAccess') as HTMLDivElement).classList.toggle('hide', this.checked);
|
(page.querySelector('.deleteAccess') as HTMLDivElement).classList.toggle('hide', this.checked);
|
||||||
});
|
});
|
||||||
|
|
||||||
window.ApiClient.getNamedConfiguration('network').then(function (config) {
|
|
||||||
(page.querySelector('.fldRemoteAccess') as HTMLDivElement).classList.toggle('hide', !config.EnableRemoteAccess);
|
|
||||||
}).catch(err => {
|
|
||||||
console.error('[useredit] failed to load network config', err);
|
|
||||||
});
|
|
||||||
|
|
||||||
(page.querySelector('.editUserProfileForm') as HTMLFormElement).addEventListener('submit', onSubmit);
|
(page.querySelector('.editUserProfileForm') as HTMLFormElement).addEventListener('submit', onSubmit);
|
||||||
|
(page.querySelector('#btnCancel') as HTMLButtonElement).addEventListener('click', onBtnCancelClick);
|
||||||
|
|
||||||
(page.querySelector('#btnCancel') as HTMLButtonElement).addEventListener('click', function() {
|
return () => {
|
||||||
window.history.back();
|
(page.querySelector('.editUserProfileForm') as HTMLFormElement).removeEventListener('submit', onSubmit);
|
||||||
});
|
(page.querySelector('#btnCancel') as HTMLButtonElement).removeEventListener('click', onBtnCancelClick);
|
||||||
}, [loadData]);
|
};
|
||||||
|
}, [loadData, updateUser, userDto, updateUserPolicy, navigate]);
|
||||||
|
|
||||||
const optionLoginProvider = authProviders.map((provider) => {
|
const optionLoginProvider = authProviders?.map((provider) => {
|
||||||
const selected = provider.Id === authenticationProviderId || authProviders.length < 2 ? ' selected' : '';
|
const selected = provider.Id === authenticationProviderId || authProviders.length < 2 ? ' selected' : '';
|
||||||
return `<option value="${provider.Id}"${selected}>${escapeHTML(provider.Name)}</option>`;
|
return `<option value="${provider.Id}"${selected}>${escapeHTML(provider.Name)}</option>`;
|
||||||
});
|
});
|
||||||
|
|
||||||
const optionPasswordResetProvider = passwordResetProviders.map((provider) => {
|
const optionPasswordResetProvider = passwordResetProviders?.map((provider) => {
|
||||||
const selected = provider.Id === passwordResetProviderId || passwordResetProviders.length < 2 ? ' selected' : '';
|
const selected = provider.Id === passwordResetProviderId || passwordResetProviders.length < 2 ? ' selected' : '';
|
||||||
return `<option value="${provider.Id}"${selected}>${escapeHTML(provider.Name)}</option>`;
|
return `<option value="${provider.Id}"${selected}>${escapeHTML(provider.Name)}</option>`;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,23 +1,25 @@
|
|||||||
import type { UserDto } from '@jellyfin/sdk/lib/generated-client';
|
import type { UserDto } from '@jellyfin/sdk/lib/generated-client';
|
||||||
import { ImageType } from '@jellyfin/sdk/lib/generated-client/models/image-type';
|
import { ImageType } from '@jellyfin/sdk/lib/generated-client/models/image-type';
|
||||||
import React, { FunctionComponent, useEffect, useState, useRef, useCallback, useMemo } from 'react';
|
import React, { FunctionComponent, useEffect, useRef, useCallback, useMemo } from 'react';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
|
|
||||||
import Dashboard from '../../../../utils/dashboard';
|
import Dashboard from '../../../../utils/dashboard';
|
||||||
import globalize from '../../../../lib/globalize';
|
import globalize from '../../../../lib/globalize';
|
||||||
import { appHost } from '../../../../components/apphost';
|
import { appHost } from '../../../../components/apphost';
|
||||||
import confirm from '../../../../components/confirm/confirm';
|
import confirm from '../../../../components/confirm/confirm';
|
||||||
import Button from '../../../../elements/emby-button/Button';
|
|
||||||
import UserPasswordForm from '../../../../components/dashboard/users/UserPasswordForm';
|
|
||||||
import loading from '../../../../components/loading/loading';
|
|
||||||
import toast from '../../../../components/toast/toast';
|
import toast from '../../../../components/toast/toast';
|
||||||
import Page from '../../../../components/Page';
|
import { useUser } from 'apps/dashboard/features/users/api/useUser';
|
||||||
import { AppFeature } from 'constants/appFeature';
|
import loading from 'components/loading/loading';
|
||||||
|
import { queryClient } from 'utils/query/queryClient';
|
||||||
|
import UserPasswordForm from 'components/dashboard/users/UserPasswordForm';
|
||||||
|
import Page from 'components/Page';
|
||||||
|
import Loading from 'components/loading/LoadingComponent';
|
||||||
|
import Button from 'elements/emby-button/Button';
|
||||||
|
|
||||||
const UserProfile: FunctionComponent = () => {
|
const UserProfile: FunctionComponent = () => {
|
||||||
const [ searchParams ] = useSearchParams();
|
const [ searchParams ] = useSearchParams();
|
||||||
const userId = searchParams.get('userId');
|
const userId = searchParams.get('userId');
|
||||||
const [ userName, setUserName ] = useState('');
|
const { data: user, isPending: isUserPending } = useUser(userId ? { userId: userId } : undefined);
|
||||||
const libraryMenu = useMemo(async () => ((await import('../../../../scripts/libraryMenu')).default), []);
|
const libraryMenu = useMemo(async () => ((await import('../../../../scripts/libraryMenu')).default), []);
|
||||||
|
|
||||||
const element = useRef<HTMLDivElement>(null);
|
const element = useRef<HTMLDivElement>(null);
|
||||||
@@ -30,50 +32,38 @@ const UserProfile: FunctionComponent = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!userId) {
|
if (!user?.Name || !user?.Id) {
|
||||||
console.error('[userprofile] missing user id');
|
throw new Error('Unexpected null user name or id');
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
loading.show();
|
void libraryMenu.then(menu => menu.setTitle(user.Name));
|
||||||
window.ApiClient.getUser(userId).then(function (user) {
|
|
||||||
if (!user.Name || !user.Id) {
|
|
||||||
throw new Error('Unexpected null user name or id');
|
|
||||||
}
|
|
||||||
|
|
||||||
setUserName(user.Name);
|
let imageUrl = 'assets/img/avatar.png';
|
||||||
void libraryMenu.then(menu => menu.setTitle(user.Name));
|
if (user.PrimaryImageTag) {
|
||||||
|
imageUrl = window.ApiClient.getUserImageUrl(user.Id, {
|
||||||
let imageUrl = 'assets/img/avatar.png';
|
tag: user.PrimaryImageTag,
|
||||||
if (user.PrimaryImageTag) {
|
type: 'Primary'
|
||||||
imageUrl = window.ApiClient.getUserImageUrl(user.Id, {
|
|
||||||
tag: user.PrimaryImageTag,
|
|
||||||
type: 'Primary'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const userImage = (page.querySelector('#image') as HTMLDivElement);
|
|
||||||
userImage.style.backgroundImage = 'url(' + imageUrl + ')';
|
|
||||||
|
|
||||||
Dashboard.getCurrentUser().then(function (loggedInUser: UserDto) {
|
|
||||||
if (!user.Policy) {
|
|
||||||
throw new Error('Unexpected null user.Policy');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (user.PrimaryImageTag) {
|
|
||||||
(page.querySelector('#btnAddImage') as HTMLButtonElement).classList.add('hide');
|
|
||||||
(page.querySelector('#btnDeleteImage') as HTMLButtonElement).classList.remove('hide');
|
|
||||||
} else if (appHost.supports(AppFeature.FileInput) && (loggedInUser?.Policy?.IsAdministrator || user.Policy.EnableUserPreferenceAccess)) {
|
|
||||||
(page.querySelector('#btnDeleteImage') as HTMLButtonElement).classList.add('hide');
|
|
||||||
(page.querySelector('#btnAddImage') as HTMLButtonElement).classList.remove('hide');
|
|
||||||
}
|
|
||||||
}).catch(err => {
|
|
||||||
console.error('[userprofile] failed to get current user', err);
|
|
||||||
});
|
});
|
||||||
loading.hide();
|
}
|
||||||
|
const userImage = (page.querySelector('#image') as HTMLDivElement);
|
||||||
|
userImage.style.backgroundImage = 'url(' + imageUrl + ')';
|
||||||
|
|
||||||
|
Dashboard.getCurrentUser().then(function (loggedInUser: UserDto) {
|
||||||
|
if (!user.Policy) {
|
||||||
|
throw new Error('Unexpected null user.Policy');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user.PrimaryImageTag) {
|
||||||
|
(page.querySelector('#btnAddImage') as HTMLButtonElement).classList.add('hide');
|
||||||
|
(page.querySelector('#btnDeleteImage') as HTMLButtonElement).classList.remove('hide');
|
||||||
|
} else if (appHost.supports('fileinput') && (loggedInUser?.Policy?.IsAdministrator || user.Policy.EnableUserPreferenceAccess)) {
|
||||||
|
(page.querySelector('#btnDeleteImage') as HTMLButtonElement).classList.add('hide');
|
||||||
|
(page.querySelector('#btnAddImage') as HTMLButtonElement).classList.remove('hide');
|
||||||
|
}
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
console.error('[userprofile] failed to load data', err);
|
console.error('[userprofile] failed to get current user', err);
|
||||||
});
|
});
|
||||||
}, [userId]);
|
}, [user, libraryMenu]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const page = element.current;
|
const page = element.current;
|
||||||
@@ -125,7 +115,9 @@ const UserProfile: FunctionComponent = () => {
|
|||||||
userImage.style.backgroundImage = 'url(' + reader.result + ')';
|
userImage.style.backgroundImage = 'url(' + reader.result + ')';
|
||||||
window.ApiClient.uploadUserImage(userId, ImageType.Primary, file).then(function () {
|
window.ApiClient.uploadUserImage(userId, ImageType.Primary, file).then(function () {
|
||||||
loading.hide();
|
loading.hide();
|
||||||
reloadUser();
|
void queryClient.invalidateQueries({
|
||||||
|
queryKey: ['User']
|
||||||
|
});
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
console.error('[userprofile] failed to upload image', err);
|
console.error('[userprofile] failed to upload image', err);
|
||||||
});
|
});
|
||||||
@@ -134,7 +126,7 @@ const UserProfile: FunctionComponent = () => {
|
|||||||
reader.readAsDataURL(file);
|
reader.readAsDataURL(file);
|
||||||
};
|
};
|
||||||
|
|
||||||
(page.querySelector('#btnDeleteImage') as HTMLButtonElement).addEventListener('click', function () {
|
const onDeleteImageClick = function () {
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
console.error('[userprofile] missing user id');
|
console.error('[userprofile] missing user id');
|
||||||
return;
|
return;
|
||||||
@@ -147,25 +139,41 @@ const UserProfile: FunctionComponent = () => {
|
|||||||
loading.show();
|
loading.show();
|
||||||
window.ApiClient.deleteUserImage(userId, ImageType.Primary).then(function () {
|
window.ApiClient.deleteUserImage(userId, ImageType.Primary).then(function () {
|
||||||
loading.hide();
|
loading.hide();
|
||||||
reloadUser();
|
void queryClient.invalidateQueries({
|
||||||
|
queryKey: ['User']
|
||||||
|
});
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
console.error('[userprofile] failed to delete image', err);
|
console.error('[userprofile] failed to delete image', err);
|
||||||
});
|
});
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
// confirm dialog closed
|
// confirm dialog closed
|
||||||
});
|
});
|
||||||
});
|
};
|
||||||
|
|
||||||
(page.querySelector('#btnAddImage') as HTMLButtonElement).addEventListener('click', function () {
|
const addImageClick = function () {
|
||||||
const uploadImage = page.querySelector('#uploadImage') as HTMLInputElement;
|
const uploadImage = page.querySelector('#uploadImage') as HTMLInputElement;
|
||||||
uploadImage.value = '';
|
uploadImage.value = '';
|
||||||
uploadImage.click();
|
uploadImage.click();
|
||||||
});
|
};
|
||||||
|
|
||||||
(page.querySelector('#uploadImage') as HTMLInputElement).addEventListener('change', function (evt: Event) {
|
const onUploadImage = (e: Event) => {
|
||||||
setFiles(evt);
|
setFiles(e);
|
||||||
});
|
};
|
||||||
}, [reloadUser, userId]);
|
|
||||||
|
(page.querySelector('#btnDeleteImage') as HTMLButtonElement).addEventListener('click', onDeleteImageClick);
|
||||||
|
(page.querySelector('#btnAddImage') as HTMLButtonElement).addEventListener('click', addImageClick);
|
||||||
|
(page.querySelector('#uploadImage') as HTMLInputElement).addEventListener('change', onUploadImage);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
(page.querySelector('#btnDeleteImage') as HTMLButtonElement).removeEventListener('click', onDeleteImageClick);
|
||||||
|
(page.querySelector('#btnAddImage') as HTMLButtonElement).removeEventListener('click', addImageClick);
|
||||||
|
(page.querySelector('#uploadImage') as HTMLInputElement).removeEventListener('change', onUploadImage);
|
||||||
|
};
|
||||||
|
}, [reloadUser, user, userId]);
|
||||||
|
|
||||||
|
if (isUserPending || !user) {
|
||||||
|
return <Loading />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page
|
<Page
|
||||||
@@ -195,7 +203,7 @@ const UserProfile: FunctionComponent = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div style={{ verticalAlign: 'top', margin: '1em 2em', display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
|
<div style={{ verticalAlign: 'top', margin: '1em 2em', display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
|
||||||
<h2 className='username' style={{ margin: 0, fontSize: 'xx-large' }}>
|
<h2 className='username' style={{ margin: 0, fontSize: 'xx-large' }}>
|
||||||
{userName}
|
{user?.Name}
|
||||||
</h2>
|
</h2>
|
||||||
<br />
|
<br />
|
||||||
<Button
|
<Button
|
||||||
@@ -213,7 +221,7 @@ const UserProfile: FunctionComponent = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<UserPasswordForm
|
<UserPasswordForm
|
||||||
userId={userId}
|
user={user}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Page>
|
</Page>
|
||||||
|
|||||||
@@ -9,12 +9,11 @@ import Button from '../../../elements/emby-button/Button';
|
|||||||
import Input from '../../../elements/emby-input/Input';
|
import Input from '../../../elements/emby-input/Input';
|
||||||
|
|
||||||
type IProps = {
|
type IProps = {
|
||||||
userId: string | null;
|
user: UserDto
|
||||||
};
|
};
|
||||||
|
|
||||||
const UserPasswordForm: FunctionComponent<IProps> = ({ userId }: IProps) => {
|
const UserPasswordForm: FunctionComponent<IProps> = ({ user }: IProps) => {
|
||||||
const element = useRef<HTMLDivElement>(null);
|
const element = useRef<HTMLDivElement>(null);
|
||||||
const user = useRef<UserDto>();
|
|
||||||
const libraryMenu = useMemo(async () => ((await import('../../../scripts/libraryMenu')).default), []);
|
const libraryMenu = useMemo(async () => ((await import('../../../scripts/libraryMenu')).default), []);
|
||||||
|
|
||||||
const loadUser = useCallback(async () => {
|
const loadUser = useCallback(async () => {
|
||||||
@@ -25,22 +24,16 @@ const UserPasswordForm: FunctionComponent<IProps> = ({ userId }: IProps) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!userId) {
|
|
||||||
console.error('[UserPasswordForm] missing user id');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
user.current = await window.ApiClient.getUser(userId);
|
|
||||||
const loggedInUser = await Dashboard.getCurrentUser();
|
const loggedInUser = await Dashboard.getCurrentUser();
|
||||||
|
|
||||||
if (!user.current.Policy || !user.current.Configuration) {
|
if (!user.Policy || !user.Configuration) {
|
||||||
throw new Error('Unexpected null user policy or configuration');
|
throw new Error('Unexpected null user policy or configuration');
|
||||||
}
|
}
|
||||||
|
|
||||||
(await libraryMenu).setTitle(user.current.Name);
|
(await libraryMenu).setTitle(user.Name);
|
||||||
|
|
||||||
if (user.current.HasConfiguredPassword) {
|
if (user.HasConfiguredPassword) {
|
||||||
if (!user.current.Policy?.IsAdministrator) {
|
if (!user.Policy?.IsAdministrator) {
|
||||||
(page.querySelector('#btnResetPassword') as HTMLDivElement).classList.remove('hide');
|
(page.querySelector('#btnResetPassword') as HTMLDivElement).classList.remove('hide');
|
||||||
}
|
}
|
||||||
(page.querySelector('#fldCurrentPassword') as HTMLDivElement).classList.remove('hide');
|
(page.querySelector('#fldCurrentPassword') as HTMLDivElement).classList.remove('hide');
|
||||||
@@ -49,7 +42,7 @@ const UserPasswordForm: FunctionComponent<IProps> = ({ userId }: IProps) => {
|
|||||||
(page.querySelector('#fldCurrentPassword') as HTMLDivElement).classList.add('hide');
|
(page.querySelector('#fldCurrentPassword') as HTMLDivElement).classList.add('hide');
|
||||||
}
|
}
|
||||||
|
|
||||||
const canChangePassword = loggedInUser?.Policy?.IsAdministrator || user.current.Policy.EnableUserPreferenceAccess;
|
const canChangePassword = loggedInUser?.Policy?.IsAdministrator || user.Policy.EnableUserPreferenceAccess;
|
||||||
(page.querySelector('.passwordSection') as HTMLDivElement).classList.toggle('hide', !canChangePassword);
|
(page.querySelector('.passwordSection') as HTMLDivElement).classList.toggle('hide', !canChangePassword);
|
||||||
|
|
||||||
import('../../autoFocuser').then(({ default: autoFocuser }) => {
|
import('../../autoFocuser').then(({ default: autoFocuser }) => {
|
||||||
@@ -61,7 +54,7 @@ const UserPasswordForm: FunctionComponent<IProps> = ({ userId }: IProps) => {
|
|||||||
(page.querySelector('#txtCurrentPassword') as HTMLInputElement).value = '';
|
(page.querySelector('#txtCurrentPassword') as HTMLInputElement).value = '';
|
||||||
(page.querySelector('#txtNewPassword') as HTMLInputElement).value = '';
|
(page.querySelector('#txtNewPassword') as HTMLInputElement).value = '';
|
||||||
(page.querySelector('#txtNewPasswordConfirm') as HTMLInputElement).value = '';
|
(page.querySelector('#txtNewPasswordConfirm') as HTMLInputElement).value = '';
|
||||||
}, [userId]);
|
}, [user, libraryMenu]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const page = element.current;
|
const page = element.current;
|
||||||
@@ -78,7 +71,7 @@ const UserPasswordForm: FunctionComponent<IProps> = ({ userId }: IProps) => {
|
|||||||
const onSubmit = (e: Event) => {
|
const onSubmit = (e: Event) => {
|
||||||
if ((page.querySelector('#txtNewPassword') as HTMLInputElement).value != (page.querySelector('#txtNewPasswordConfirm') as HTMLInputElement).value) {
|
if ((page.querySelector('#txtNewPassword') as HTMLInputElement).value != (page.querySelector('#txtNewPasswordConfirm') as HTMLInputElement).value) {
|
||||||
toast(globalize.translate('PasswordMatchError'));
|
toast(globalize.translate('PasswordMatchError'));
|
||||||
} else if ((page.querySelector('#txtNewPassword') as HTMLInputElement).value == '' && user.current?.Policy?.IsAdministrator) {
|
} else if ((page.querySelector('#txtNewPassword') as HTMLInputElement).value == '' && user?.Policy?.IsAdministrator) {
|
||||||
toast(globalize.translate('PasswordMissingSaveError'));
|
toast(globalize.translate('PasswordMissingSaveError'));
|
||||||
} else {
|
} else {
|
||||||
loading.show();
|
loading.show();
|
||||||
@@ -90,7 +83,7 @@ const UserPasswordForm: FunctionComponent<IProps> = ({ userId }: IProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const savePassword = () => {
|
const savePassword = () => {
|
||||||
if (!userId) {
|
if (!user.Id) {
|
||||||
console.error('[UserPasswordForm.savePassword] missing user id');
|
console.error('[UserPasswordForm.savePassword] missing user id');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -104,7 +97,7 @@ const UserPasswordForm: FunctionComponent<IProps> = ({ userId }: IProps) => {
|
|||||||
currentPassword = '';
|
currentPassword = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
window.ApiClient.updateUserPassword(userId, currentPassword, newPassword).then(function () {
|
window.ApiClient.updateUserPassword(user.Id, currentPassword, newPassword).then(function () {
|
||||||
loading.hide();
|
loading.hide();
|
||||||
toast(globalize.translate('PasswordSaved'));
|
toast(globalize.translate('PasswordSaved'));
|
||||||
|
|
||||||
@@ -121,26 +114,23 @@ const UserPasswordForm: FunctionComponent<IProps> = ({ userId }: IProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const resetPassword = () => {
|
const resetPassword = () => {
|
||||||
if (!userId) {
|
|
||||||
console.error('[UserPasswordForm.resetPassword] missing user id');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const msg = globalize.translate('PasswordResetConfirmation');
|
const msg = globalize.translate('PasswordResetConfirmation');
|
||||||
confirm(msg, globalize.translate('ResetPassword')).then(function () {
|
confirm(msg, globalize.translate('ResetPassword')).then(function () {
|
||||||
loading.show();
|
loading.show();
|
||||||
window.ApiClient.resetUserPassword(userId).then(function () {
|
if (user.Id) {
|
||||||
loading.hide();
|
window.ApiClient.resetUserPassword(user.Id).then(function () {
|
||||||
Dashboard.alert({
|
loading.hide();
|
||||||
message: globalize.translate('PasswordResetComplete'),
|
Dashboard.alert({
|
||||||
title: globalize.translate('ResetPassword')
|
message: globalize.translate('PasswordResetComplete'),
|
||||||
|
title: globalize.translate('ResetPassword')
|
||||||
|
});
|
||||||
|
loadUser().catch(err => {
|
||||||
|
console.error('[UserPasswordForm] failed to load user', err);
|
||||||
|
});
|
||||||
|
}).catch(err => {
|
||||||
|
console.error('[UserPasswordForm] failed to reset user password', err);
|
||||||
});
|
});
|
||||||
loadUser().catch(err => {
|
}
|
||||||
console.error('[UserPasswordForm] failed to load user', err);
|
|
||||||
});
|
|
||||||
}).catch(err => {
|
|
||||||
console.error('[UserPasswordForm] failed to reset user password', err);
|
|
||||||
});
|
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
// confirm dialog was closed
|
// confirm dialog was closed
|
||||||
});
|
});
|
||||||
@@ -148,7 +138,12 @@ const UserPasswordForm: FunctionComponent<IProps> = ({ userId }: IProps) => {
|
|||||||
|
|
||||||
(page.querySelector('.updatePasswordForm') as HTMLFormElement).addEventListener('submit', onSubmit);
|
(page.querySelector('.updatePasswordForm') as HTMLFormElement).addEventListener('submit', onSubmit);
|
||||||
(page.querySelector('#btnResetPassword') as HTMLButtonElement).addEventListener('click', resetPassword);
|
(page.querySelector('#btnResetPassword') as HTMLButtonElement).addEventListener('click', resetPassword);
|
||||||
}, [loadUser, userId]);
|
|
||||||
|
return () => {
|
||||||
|
(page.querySelector('.updatePasswordForm') as HTMLFormElement).removeEventListener('submit', onSubmit);
|
||||||
|
(page.querySelector('#btnResetPassword') as HTMLButtonElement).removeEventListener('click', resetPassword);
|
||||||
|
};
|
||||||
|
}, [loadUser, user]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={element}>
|
<div ref={element}>
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import { useApi } from './useApi';
|
|||||||
|
|
||||||
export type UsersRecords = Record<string, UserDto>;
|
export type UsersRecords = Record<string, UserDto>;
|
||||||
|
|
||||||
|
export const QUERY_KEY = 'Users';
|
||||||
|
|
||||||
const fetchUsers = async (
|
const fetchUsers = async (
|
||||||
api: Api,
|
api: Api,
|
||||||
requestParams?: UserApiGetUsersRequest,
|
requestParams?: UserApiGetUsersRequest,
|
||||||
@@ -23,7 +25,7 @@ const fetchUsers = async (
|
|||||||
export const useUsers = (requestParams?: UserApiGetUsersRequest) => {
|
export const useUsers = (requestParams?: UserApiGetUsersRequest) => {
|
||||||
const { api } = useApi();
|
const { api } = useApi();
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ['Users'],
|
queryKey: [ QUERY_KEY ],
|
||||||
queryFn: ({ signal }) =>
|
queryFn: ({ signal }) =>
|
||||||
fetchUsers(api!, requestParams, { signal }),
|
fetchUsers(api!, requestParams, { signal }),
|
||||||
enabled: !!api
|
enabled: !!api
|
||||||
|
|||||||
Reference in New Issue
Block a user