mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-14 10:34:06 +02:00
Fix any type usage in proprietary/ (#5949)
# Description of Changes Follow on from #5934, expanding `any` type usage ban to the `proprietary/` folder
This commit is contained in:
@@ -83,7 +83,10 @@ export default defineConfig(
|
|||||||
},
|
},
|
||||||
// Folders that have been cleaned up and are now conformant - stricter rules enforced here
|
// Folders that have been cleaned up and are now conformant - stricter rules enforced here
|
||||||
{
|
{
|
||||||
files: ['src/saas/**/*.{js,mjs,jsx,ts,tsx}'],
|
files: [
|
||||||
|
'src/proprietary/**/*.{js,mjs,jsx,ts,tsx}',
|
||||||
|
'src/saas/**/*.{js,mjs,jsx,ts,tsx}',
|
||||||
|
],
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
project: true,
|
project: true,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
|
|||||||
import { springAuth } from '@app/auth/springAuthClient';
|
import { springAuth } from '@app/auth/springAuthClient';
|
||||||
import { startOAuthNavigation } from '@app/extensions/oauthNavigation';
|
import { startOAuthNavigation } from '@app/extensions/oauthNavigation';
|
||||||
import apiClient from '@app/services/apiClient';
|
import apiClient from '@app/services/apiClient';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError, type AxiosResponse, type InternalAxiosRequestConfig } from 'axios';
|
||||||
|
|
||||||
// Mock apiClient
|
// Mock apiClient
|
||||||
vi.mock('@app/services/apiClient');
|
vi.mock('@app/services/apiClient');
|
||||||
@@ -45,7 +45,7 @@ describe('SpringAuthClient', () => {
|
|||||||
vi.mocked(apiClient.get).mockResolvedValueOnce({
|
vi.mocked(apiClient.get).mockResolvedValueOnce({
|
||||||
status: 200,
|
status: 200,
|
||||||
data: { user: mockUser },
|
data: { user: mockUser },
|
||||||
} as any);
|
} as unknown as AxiosResponse);
|
||||||
|
|
||||||
const result = await springAuth.getSession();
|
const result = await springAuth.getSession();
|
||||||
|
|
||||||
@@ -74,7 +74,7 @@ describe('SpringAuthClient', () => {
|
|||||||
statusText: 'Unauthorized',
|
statusText: 'Unauthorized',
|
||||||
data: {},
|
data: {},
|
||||||
headers: {},
|
headers: {},
|
||||||
config: {} as any,
|
config: {} as InternalAxiosRequestConfig,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@ describe('SpringAuthClient', () => {
|
|||||||
statusText: 'Forbidden',
|
statusText: 'Forbidden',
|
||||||
data: {},
|
data: {},
|
||||||
headers: {},
|
headers: {},
|
||||||
config: {} as any,
|
config: {} as InternalAxiosRequestConfig,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -141,7 +141,7 @@ describe('SpringAuthClient', () => {
|
|||||||
expires_in: 3600,
|
expires_in: 3600,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as any);
|
} as unknown as AxiosResponse);
|
||||||
|
|
||||||
// Spy on window.dispatchEvent
|
// Spy on window.dispatchEvent
|
||||||
const dispatchEventSpy = vi.spyOn(window, 'dispatchEvent');
|
const dispatchEventSpy = vi.spyOn(window, 'dispatchEvent');
|
||||||
@@ -208,7 +208,7 @@ describe('SpringAuthClient', () => {
|
|||||||
vi.mocked(apiClient.post).mockResolvedValueOnce({
|
vi.mocked(apiClient.post).mockResolvedValueOnce({
|
||||||
status: 200,
|
status: 200,
|
||||||
data: { user: mockUser },
|
data: { user: mockUser },
|
||||||
} as any);
|
} as unknown as AxiosResponse);
|
||||||
|
|
||||||
const result = await springAuth.signUp(credentials);
|
const result = await springAuth.signUp(credentials);
|
||||||
|
|
||||||
@@ -259,7 +259,7 @@ describe('SpringAuthClient', () => {
|
|||||||
vi.mocked(apiClient.post).mockResolvedValueOnce({
|
vi.mocked(apiClient.post).mockResolvedValueOnce({
|
||||||
status: 200,
|
status: 200,
|
||||||
data: {},
|
data: {},
|
||||||
} as any);
|
} as unknown as AxiosResponse);
|
||||||
|
|
||||||
const result = await springAuth.signOut();
|
const result = await springAuth.signOut();
|
||||||
|
|
||||||
@@ -308,7 +308,7 @@ describe('SpringAuthClient', () => {
|
|||||||
expires_in: 3600,
|
expires_in: 3600,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as any);
|
} as unknown as AxiosResponse);
|
||||||
|
|
||||||
const result = await springAuth.refreshSession();
|
const result = await springAuth.refreshSession();
|
||||||
|
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ export interface User {
|
|||||||
is_anonymous?: boolean;
|
is_anonymous?: boolean;
|
||||||
isFirstLogin?: boolean;
|
isFirstLogin?: boolean;
|
||||||
authenticationType?: string;
|
authenticationType?: string;
|
||||||
app_metadata?: Record<string, any>;
|
app_metadata?: Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Session {
|
export interface Session {
|
||||||
@@ -447,7 +447,7 @@ class SpringAuthClient {
|
|||||||
*/
|
*/
|
||||||
async signInWithOAuth(params: {
|
async signInWithOAuth(params: {
|
||||||
provider: OAuthProvider;
|
provider: OAuthProvider;
|
||||||
options?: { redirectTo?: string; queryParams?: Record<string, any> };
|
options?: { redirectTo?: string; queryParams?: Record<string, string> };
|
||||||
}): Promise<{ error: AuthError | null }> {
|
}): Promise<{ error: AuthError | null }> {
|
||||||
try {
|
try {
|
||||||
const redirectPath = normalizeRedirectPath(params.options?.redirectTo);
|
const redirectPath = normalizeRedirectPath(params.options?.redirectTo);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
|
import { isAxiosError } from 'axios';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
ActionIcon,
|
ActionIcon,
|
||||||
@@ -125,8 +126,10 @@ export default function ChangeUserPasswordModal({ opened, onClose, user, onSucce
|
|||||||
alert({ alertType: 'success', title: t('workspace.people.changePassword.success', 'Password updated successfully') });
|
alert({ alertType: 'success', title: t('workspace.people.changePassword.success', 'Password updated successfully') });
|
||||||
onSuccess();
|
onSuccess();
|
||||||
handleClose();
|
handleClose();
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
const errorMessage = error.response?.data?.message || error.response?.data?.error || error.message || t('workspace.people.changePassword.error', 'Failed to update password');
|
const errorMessage = isAxiosError(error)
|
||||||
|
? (error.response?.data?.message || error.response?.data?.error || error.message)
|
||||||
|
: (error instanceof Error ? error.message : undefined) || t('workspace.people.changePassword.error', 'Failed to update password');
|
||||||
alert({ alertType: 'error', title: errorMessage });
|
alert({ alertType: 'error', title: errorMessage });
|
||||||
} finally {
|
} finally {
|
||||||
setProcessing(false);
|
setProcessing(false);
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ interface TextDividerProps {
|
|||||||
export default function DividerWithText({ text, className = '', style, variant = 'default', respondsToDarkMode = true, opacity }: TextDividerProps) {
|
export default function DividerWithText({ text, className = '', style, variant = 'default', respondsToDarkMode = true, opacity }: TextDividerProps) {
|
||||||
const variantClass = variant === 'subcategory' ? 'subcategory' : '';
|
const variantClass = variant === 'subcategory' ? 'subcategory' : '';
|
||||||
const themeClass = respondsToDarkMode ? '' : 'force-light';
|
const themeClass = respondsToDarkMode ? '' : 'force-light';
|
||||||
const styleWithOpacity = opacity !== undefined ? { ...(style || {}), ['--text-divider-opacity' as any]: opacity } : style;
|
const styleWithOpacity = opacity !== undefined ? { ...(style || {}), ['--text-divider-opacity' as string]: opacity } : style;
|
||||||
|
|
||||||
if (text) {
|
if (text) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useState, useEffect, useRef } from 'react';
|
import { useState, useEffect, useRef } from 'react';
|
||||||
|
import { isAxiosError } from 'axios';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
Modal,
|
Modal,
|
||||||
@@ -158,9 +159,11 @@ export default function InviteMembersModal({ opened, onClose, onSuccess }: Invit
|
|||||||
forceChange: false,
|
forceChange: false,
|
||||||
forceMFA: false,
|
forceMFA: false,
|
||||||
});
|
});
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
console.error('Failed to invite user:', error);
|
console.error('Failed to invite user:', error);
|
||||||
const errorMessage = error.response?.data?.message || error.response?.data?.error || error.message || t('workspace.people.addMember.error');
|
const errorMessage = isAxiosError(error)
|
||||||
|
? (error.response?.data?.message || error.response?.data?.error || error.message)
|
||||||
|
: (error instanceof Error ? error.message : undefined) || t('workspace.people.addMember.error');
|
||||||
alert({ alertType: 'error', title: errorMessage });
|
alert({ alertType: 'error', title: errorMessage });
|
||||||
} finally {
|
} finally {
|
||||||
setProcessing(false);
|
setProcessing(false);
|
||||||
@@ -211,12 +214,11 @@ export default function InviteMembersModal({ opened, onClose, onSuccess }: Invit
|
|||||||
body: response.errors || response.error
|
body: response.errors || response.error
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
console.error('Failed to invite users:', error);
|
console.error('Failed to invite users:', error);
|
||||||
const errorMessage = error.response?.data?.message ||
|
const errorMessage = isAxiosError(error)
|
||||||
error.response?.data?.error ||
|
? (error.response?.data?.message || error.response?.data?.error || error.message)
|
||||||
error.message ||
|
: (error instanceof Error ? error.message : undefined) || t('workspace.people.emailInvite.error', 'Failed to send invites');
|
||||||
t('workspace.people.emailInvite.error', 'Failed to send invites');
|
|
||||||
alert({ alertType: 'error', title: errorMessage });
|
alert({ alertType: 'error', title: errorMessage });
|
||||||
} finally {
|
} finally {
|
||||||
setProcessing(false);
|
setProcessing(false);
|
||||||
@@ -239,9 +241,11 @@ export default function InviteMembersModal({ opened, onClose, onSuccess }: Invit
|
|||||||
if (inviteLinkForm.sendEmail && inviteLinkForm.email) {
|
if (inviteLinkForm.sendEmail && inviteLinkForm.email) {
|
||||||
alert({ alertType: 'success', title: t('workspace.people.inviteLink.emailSent', 'Invite link generated and sent via email') });
|
alert({ alertType: 'success', title: t('workspace.people.inviteLink.emailSent', 'Invite link generated and sent via email') });
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
console.error('Failed to generate invite link:', error);
|
console.error('Failed to generate invite link:', error);
|
||||||
const errorMessage = error.response?.data?.message || error.response?.data?.error || error.message || t('workspace.people.inviteLink.error', 'Failed to generate invite link');
|
const errorMessage = isAxiosError(error)
|
||||||
|
? (error.response?.data?.message || error.response?.data?.error || error.message)
|
||||||
|
: (error instanceof Error ? error.message : undefined) || t('workspace.people.inviteLink.error', 'Failed to generate invite link');
|
||||||
alert({ alertType: 'error', title: errorMessage });
|
alert({ alertType: 'error', title: errorMessage });
|
||||||
} finally {
|
} finally {
|
||||||
setProcessing(false);
|
setProcessing(false);
|
||||||
|
|||||||
@@ -34,12 +34,12 @@ export const ManageBillingButton: React.FC<ManageBillingButtonProps> = ({
|
|||||||
// Open billing portal in new tab
|
// Open billing portal in new tab
|
||||||
window.open(response.url, '_blank');
|
window.open(response.url, '_blank');
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
console.error('Failed to open billing portal:', error);
|
console.error('Failed to open billing portal:', error);
|
||||||
alert({
|
alert({
|
||||||
alertType: 'error',
|
alertType: 'error',
|
||||||
title: t('billing.portal.error', 'Failed to open billing portal'),
|
title: t('billing.portal.error', 'Failed to open billing portal'),
|
||||||
body: error.message || 'Please try again or contact support.',
|
body: (error instanceof Error ? error.message : undefined) || 'Please try again or contact support.',
|
||||||
});
|
});
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ const UpgradeBanner: React.FC = () => {
|
|||||||
const scenarioProvidesInfo =
|
const scenarioProvidesInfo =
|
||||||
scenarioKey && scenarioKey !== 'unknown' && scenarioKey !== 'licensed';
|
scenarioKey && scenarioKey !== 'unknown' && scenarioKey !== 'licensed';
|
||||||
const derivedIsAdmin = scenarioProvidesInfo
|
const derivedIsAdmin = scenarioProvidesInfo
|
||||||
? scenarioKey!.includes('admin')
|
? scenarioKey.includes('admin')
|
||||||
: isAdmin;
|
: isAdmin;
|
||||||
const derivedHasPaidLicense =
|
const derivedHasPaidLicense =
|
||||||
scenarioKey === 'licensed'
|
scenarioKey === 'licensed'
|
||||||
@@ -95,10 +95,10 @@ const UpgradeBanner: React.FC = () => {
|
|||||||
? hasPaidLicense
|
? hasPaidLicense
|
||||||
: false;
|
: false;
|
||||||
const derivedIsUnderLimit = scenarioProvidesInfo
|
const derivedIsUnderLimit = scenarioProvidesInfo
|
||||||
? scenarioKey!.includes('under-limit')
|
? scenarioKey.includes('under-limit')
|
||||||
: isUnderLimit === true;
|
: isUnderLimit === true;
|
||||||
const derivedIsOverLimit = scenarioProvidesInfo
|
const derivedIsOverLimit = scenarioProvidesInfo
|
||||||
? scenarioKey!.includes('over-limit')
|
? scenarioKey.includes('over-limit')
|
||||||
: isOverLimit === true;
|
: isOverLimit === true;
|
||||||
|
|
||||||
const effectiveIsAdmin = scenario
|
const effectiveIsAdmin = scenario
|
||||||
|
|||||||
+9
-9
@@ -1,4 +1,5 @@
|
|||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
import { isAxiosError } from 'axios';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { NumberInput, Switch, Button, Stack, Paper, Text, Loader, Group, Accordion, TextInput, MultiSelect } from '@mantine/core';
|
import { NumberInput, Switch, Button, Stack, Paper, Text, Loader, Group, Accordion, TextInput, MultiSelect } from '@mantine/core';
|
||||||
import { alert } from '@app/components/toast';
|
import { alert } from '@app/components/toast';
|
||||||
@@ -72,7 +73,7 @@ export default function AdminAdvancedSection() {
|
|||||||
isFieldPending,
|
isFieldPending,
|
||||||
} = useAdminSettings<AdvancedSettingsData>({
|
} = useAdminSettings<AdvancedSettingsData>({
|
||||||
sectionName: 'advanced',
|
sectionName: 'advanced',
|
||||||
fetchTransformer: async (): Promise<AdvancedSettingsData & { _pending?: Record<string, any> }> => {
|
fetchTransformer: async (): Promise<AdvancedSettingsData & { _pending?: Record<string, unknown> }> => {
|
||||||
const [systemResponse, processExecutorResponse] = await Promise.all([
|
const [systemResponse, processExecutorResponse] = await Promise.all([
|
||||||
apiClient.get('/api/v1/admin/settings/section/system'),
|
apiClient.get('/api/v1/admin/settings/section/system'),
|
||||||
apiClient.get('/api/v1/admin/settings/section/processExecutor')
|
apiClient.get('/api/v1/admin/settings/section/processExecutor')
|
||||||
@@ -81,7 +82,7 @@ export default function AdminAdvancedSection() {
|
|||||||
const systemData = systemResponse.data || {};
|
const systemData = systemResponse.data || {};
|
||||||
const processExecutorData = processExecutorResponse.data || {};
|
const processExecutorData = processExecutorResponse.data || {};
|
||||||
|
|
||||||
const result: AdvancedSettingsData & { _pending?: Record<string, any> } = {
|
const result: AdvancedSettingsData & { _pending?: Record<string, unknown> } = {
|
||||||
enableAlphaFunctionality: systemData.enableAlphaFunctionality || false,
|
enableAlphaFunctionality: systemData.enableAlphaFunctionality || false,
|
||||||
maxDPI: systemData.maxDPI || 0,
|
maxDPI: systemData.maxDPI || 0,
|
||||||
enableUrlToPDF: systemData.enableUrlToPDF || false,
|
enableUrlToPDF: systemData.enableUrlToPDF || false,
|
||||||
@@ -101,7 +102,7 @@ export default function AdminAdvancedSection() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Merge pending blocks from both endpoints
|
// Merge pending blocks from both endpoints
|
||||||
const pendingBlock: Record<string, any> = {};
|
const pendingBlock: Record<string, unknown> = {};
|
||||||
if (systemData._pending?.enableAlphaFunctionality !== undefined) {
|
if (systemData._pending?.enableAlphaFunctionality !== undefined) {
|
||||||
pendingBlock.enableAlphaFunctionality = systemData._pending.enableAlphaFunctionality;
|
pendingBlock.enableAlphaFunctionality = systemData._pending.enableAlphaFunctionality;
|
||||||
}
|
}
|
||||||
@@ -131,7 +132,7 @@ export default function AdminAdvancedSection() {
|
|||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
saveTransformer: (settings) => {
|
saveTransformer: (settings) => {
|
||||||
const deltaSettings: Record<string, any> = {
|
const deltaSettings: Record<string, unknown> = {
|
||||||
'system.enableAlphaFunctionality': settings.enableAlphaFunctionality,
|
'system.enableAlphaFunctionality': settings.enableAlphaFunctionality,
|
||||||
'system.maxDPI': settings.maxDPI,
|
'system.maxDPI': settings.maxDPI,
|
||||||
'system.enableUrlToPDF': settings.enableUrlToPDF,
|
'system.enableUrlToPDF': settings.enableUrlToPDF,
|
||||||
@@ -281,9 +282,8 @@ export default function AdminAdvancedSection() {
|
|||||||
setManualDownloadLinks([]);
|
setManualDownloadLinks([]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[AdminAdvancedSection] Download tessdata languages failed', error);
|
console.error('[AdminAdvancedSection] Download tessdata languages failed', error);
|
||||||
const response = (error as any)?.response;
|
const status = isAxiosError(error) ? error.response?.status : undefined;
|
||||||
const status = response?.status;
|
const serverMessage = isAxiosError(error) ? error.response?.data?.message : undefined;
|
||||||
const serverMessage = response?.data?.message;
|
|
||||||
|
|
||||||
if (status === 403) {
|
if (status === 403) {
|
||||||
console.warn('[AdminAdvancedSection] Tessdata directory not writable, falling back to manual download:', serverMessage);
|
console.warn('[AdminAdvancedSection] Tessdata directory not writable, falling back to manual download:', serverMessage);
|
||||||
@@ -309,12 +309,12 @@ export default function AdminAdvancedSection() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let message: string;
|
let message: string;
|
||||||
if (!response) {
|
if (!isAxiosError(error) || !error.response) {
|
||||||
message = t(
|
message = t(
|
||||||
'admin.settings.advanced.tessdataDir.downloadErrorNetwork',
|
'admin.settings.advanced.tessdataDir.downloadErrorNetwork',
|
||||||
'Download failed due to a network error. Please check your connection and try again.'
|
'Download failed due to a network error. Please check your connection and try again.'
|
||||||
);
|
);
|
||||||
} else if (status >= 500) {
|
} else if (status !== undefined && status >= 500) {
|
||||||
message = t(
|
message = t(
|
||||||
'admin.settings.advanced.tessdataDir.downloadErrorServer',
|
'admin.settings.advanced.tessdataDir.downloadErrorServer',
|
||||||
'The server encountered an error while downloading tessdata languages. Please try again later.'
|
'The server encountered an error while downloading tessdata languages. Please try again later.'
|
||||||
|
|||||||
+3
-2
@@ -1,4 +1,5 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { isAxiosError } from 'axios';
|
||||||
import { Tabs, Loader, Alert, Stack, Text, Button, Accordion } from '@mantine/core';
|
import { Tabs, Loader, Alert, Stack, Text, Button, Accordion } from '@mantine/core';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
@@ -35,9 +36,9 @@ const AdminAuditSection: React.FC = () => {
|
|||||||
setError(null);
|
setError(null);
|
||||||
const status = await auditService.getSystemStatus();
|
const status = await auditService.getSystemStatus();
|
||||||
setSystemStatus(status);
|
setSystemStatus(status);
|
||||||
} catch (err: any) {
|
} catch (err: unknown) {
|
||||||
// Check if this is a permission/license error (403/404)
|
// Check if this is a permission/license error (403/404)
|
||||||
const status = err?.response?.status;
|
const status = isAxiosError(err) ? err.response?.status : undefined;
|
||||||
if (status === 403 || status === 404) {
|
if (status === 403 || status === 404) {
|
||||||
setError('enterprise-license-required');
|
setError('enterprise-license-required');
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+96
-41
@@ -44,6 +44,66 @@ interface TelegramSettingsData {
|
|||||||
feedback?: FeedbackSettings;
|
feedback?: FeedbackSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface MailSettings {
|
||||||
|
enabled?: boolean;
|
||||||
|
enableInvites?: boolean;
|
||||||
|
host?: string;
|
||||||
|
port?: number;
|
||||||
|
username?: string;
|
||||||
|
password?: string;
|
||||||
|
from?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GoogleDriveSettings {
|
||||||
|
enabled?: boolean;
|
||||||
|
clientId?: string;
|
||||||
|
apiKey?: string;
|
||||||
|
appId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OAuth2GenericSettings {
|
||||||
|
enabled?: boolean;
|
||||||
|
provider?: string;
|
||||||
|
issuer?: string;
|
||||||
|
clientId?: string;
|
||||||
|
clientSecret?: string;
|
||||||
|
scopes?: string;
|
||||||
|
useAsUsername?: string;
|
||||||
|
autoCreateUser?: boolean;
|
||||||
|
blockRegistration?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Saml2Settings {
|
||||||
|
enabled?: boolean;
|
||||||
|
provider?: string;
|
||||||
|
registrationId?: string;
|
||||||
|
idpMetadataUri?: string;
|
||||||
|
idpSingleLoginUrl?: string;
|
||||||
|
idpSingleLogoutUrl?: string;
|
||||||
|
idpIssuer?: string;
|
||||||
|
idpCert?: string;
|
||||||
|
privateKey?: string;
|
||||||
|
spCert?: string;
|
||||||
|
autoCreateUser?: boolean;
|
||||||
|
blockRegistration?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OAuth2ClientSettings {
|
||||||
|
clientId?: string;
|
||||||
|
clientSecret?: string;
|
||||||
|
scopes?: string;
|
||||||
|
useAsUsername?: string;
|
||||||
|
issuer?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProviderSettings =
|
||||||
|
| MailSettings
|
||||||
|
| TelegramSettingsData
|
||||||
|
| GoogleDriveSettings
|
||||||
|
| OAuth2GenericSettings
|
||||||
|
| Saml2Settings
|
||||||
|
| OAuth2ClientSettings;
|
||||||
|
|
||||||
interface ConnectionsSettingsData {
|
interface ConnectionsSettingsData {
|
||||||
oauth2?: {
|
oauth2?: {
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
@@ -55,22 +115,10 @@ interface ConnectionsSettingsData {
|
|||||||
blockRegistration?: boolean;
|
blockRegistration?: boolean;
|
||||||
useAsUsername?: string;
|
useAsUsername?: string;
|
||||||
scopes?: string;
|
scopes?: string;
|
||||||
client?: {
|
client?: Record<string, OAuth2ClientSettings>;
|
||||||
[key: string]: any;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
saml2?: {
|
|
||||||
[key: string]: any;
|
|
||||||
};
|
|
||||||
mail?: {
|
|
||||||
enabled?: boolean;
|
|
||||||
enableInvites?: boolean;
|
|
||||||
host?: string;
|
|
||||||
port?: number;
|
|
||||||
username?: string;
|
|
||||||
password?: string;
|
|
||||||
from?: string;
|
|
||||||
};
|
};
|
||||||
|
saml2?: Saml2Settings;
|
||||||
|
mail?: MailSettings;
|
||||||
telegram?: TelegramSettingsData;
|
telegram?: TelegramSettingsData;
|
||||||
ssoAutoLogin?: boolean;
|
ssoAutoLogin?: boolean;
|
||||||
enableMobileScanner?: boolean;
|
enableMobileScanner?: boolean;
|
||||||
@@ -93,7 +141,7 @@ export default function AdminConnectionsSection() {
|
|||||||
|
|
||||||
const adminSettings = useAdminSettings<ConnectionsSettingsData>({
|
const adminSettings = useAdminSettings<ConnectionsSettingsData>({
|
||||||
sectionName: 'connections',
|
sectionName: 'connections',
|
||||||
fetchTransformer: async (): Promise<ConnectionsSettingsData & { _pending?: Record<string, any> }> => {
|
fetchTransformer: async (): Promise<ConnectionsSettingsData & { _pending?: Record<string, unknown> }> => {
|
||||||
// Fetch security settings (oauth2, saml2)
|
// Fetch security settings (oauth2, saml2)
|
||||||
const securityResponse = await apiClient.get('/api/v1/admin/settings/section/security');
|
const securityResponse = await apiClient.get('/api/v1/admin/settings/section/security');
|
||||||
const securityData = securityResponse.data || {};
|
const securityData = securityResponse.data || {};
|
||||||
@@ -114,7 +162,7 @@ export default function AdminConnectionsSection() {
|
|||||||
const systemResponse = await apiClient.get('/api/v1/admin/settings/section/system');
|
const systemResponse = await apiClient.get('/api/v1/admin/settings/section/system');
|
||||||
const systemData = systemResponse.data || {};
|
const systemData = systemResponse.data || {};
|
||||||
|
|
||||||
const result: ConnectionsSettingsData & { _pending?: Record<string, any> } = {
|
const result: ConnectionsSettingsData & { _pending?: Record<string, unknown> } = {
|
||||||
oauth2: securityData.oauth2 || {},
|
oauth2: securityData.oauth2 || {},
|
||||||
saml2: securityData.saml2 || {},
|
saml2: securityData.saml2 || {},
|
||||||
mail: mailData || {},
|
mail: mailData || {},
|
||||||
@@ -132,7 +180,7 @@ export default function AdminConnectionsSection() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Merge pending blocks from all endpoints
|
// Merge pending blocks from all endpoints
|
||||||
const pendingBlock: Record<string, any> = {};
|
const pendingBlock: Record<string, unknown> = {};
|
||||||
if (securityData._pending?.oauth2) {
|
if (securityData._pending?.oauth2) {
|
||||||
pendingBlock.oauth2 = securityData._pending.oauth2;
|
pendingBlock.oauth2 = securityData._pending.oauth2;
|
||||||
}
|
}
|
||||||
@@ -183,13 +231,13 @@ export default function AdminConnectionsSection() {
|
|||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
saveTransformer: (currentSettings: ConnectionsSettingsData) => {
|
saveTransformer: (currentSettings: ConnectionsSettingsData) => {
|
||||||
const deltaSettings: Record<string, any> = {};
|
const deltaSettings: Record<string, unknown> = {};
|
||||||
|
|
||||||
// Build delta for oauth2 settings
|
// Build delta for oauth2 settings
|
||||||
if (currentSettings.oauth2) {
|
if (currentSettings.oauth2) {
|
||||||
Object.keys(currentSettings.oauth2).forEach((key) => {
|
Object.keys(currentSettings.oauth2).forEach((key) => {
|
||||||
if (key !== 'client') {
|
if (key !== 'client') {
|
||||||
deltaSettings[`security.oauth2.${key}`] = (currentSettings.oauth2 as Record<string, any>)[key];
|
deltaSettings[`security.oauth2.${key}`] = (currentSettings.oauth2 as Record<string, unknown>)[key];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -197,7 +245,7 @@ export default function AdminConnectionsSection() {
|
|||||||
const oauth2Client = currentSettings.oauth2.client;
|
const oauth2Client = currentSettings.oauth2.client;
|
||||||
if (oauth2Client) {
|
if (oauth2Client) {
|
||||||
Object.keys(oauth2Client).forEach((providerId) => {
|
Object.keys(oauth2Client).forEach((providerId) => {
|
||||||
const providerSettings = oauth2Client[providerId];
|
const providerSettings = oauth2Client[providerId] as Record<string, unknown>;
|
||||||
Object.keys(providerSettings).forEach((key) => {
|
Object.keys(providerSettings).forEach((key) => {
|
||||||
deltaSettings[`security.oauth2.client.${providerId}.${key}`] = providerSettings[key];
|
deltaSettings[`security.oauth2.client.${providerId}.${key}`] = providerSettings[key];
|
||||||
});
|
});
|
||||||
@@ -207,22 +255,25 @@ export default function AdminConnectionsSection() {
|
|||||||
|
|
||||||
// Build delta for saml2 settings
|
// Build delta for saml2 settings
|
||||||
if (currentSettings.saml2) {
|
if (currentSettings.saml2) {
|
||||||
Object.keys(currentSettings.saml2).forEach((key) => {
|
const saml2 = currentSettings.saml2 as Record<string, unknown>;
|
||||||
deltaSettings[`security.saml2.${key}`] = (currentSettings.saml2 as Record<string, any>)[key];
|
Object.keys(saml2).forEach((key) => {
|
||||||
|
deltaSettings[`security.saml2.${key}`] = saml2[key];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mail settings
|
// Mail settings
|
||||||
if (currentSettings.mail) {
|
if (currentSettings.mail) {
|
||||||
Object.keys(currentSettings.mail).forEach((key) => {
|
const mail = currentSettings.mail as Record<string, unknown>;
|
||||||
deltaSettings[`mail.${key}`] = (currentSettings.mail as Record<string, any>)[key];
|
Object.keys(mail).forEach((key) => {
|
||||||
|
deltaSettings[`mail.${key}`] = mail[key];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Telegram settings
|
// Telegram settings
|
||||||
if (currentSettings.telegram) {
|
if (currentSettings.telegram) {
|
||||||
Object.keys(currentSettings.telegram).forEach((key) => {
|
const telegram = currentSettings.telegram as Record<string, unknown>;
|
||||||
deltaSettings[`telegram.${key}`] = (currentSettings.telegram as Record<string, any>)[key];
|
Object.keys(telegram).forEach((key) => {
|
||||||
|
deltaSettings[`telegram.${key}`] = telegram[key];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -332,7 +383,7 @@ export default function AdminConnectionsSection() {
|
|||||||
return !!(providerSettings?.clientId);
|
return !!(providerSettings?.clientId);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getProviderSettings = (provider: Provider): Record<string, any> => {
|
const getProviderSettings = (provider: Provider): ProviderSettings => {
|
||||||
if (provider.id === 'saml2') {
|
if (provider.id === 'saml2') {
|
||||||
return settings?.saml2 || {};
|
return settings?.saml2 || {};
|
||||||
}
|
}
|
||||||
@@ -346,17 +397,17 @@ export default function AdminConnectionsSection() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (provider.id === 'googledrive') {
|
if (provider.id === 'googledrive') {
|
||||||
return {
|
const gd: GoogleDriveSettings = {
|
||||||
enabled: settings?.googleDriveEnabled,
|
enabled: settings?.googleDriveEnabled,
|
||||||
clientId: settings?.googleDriveClientId,
|
clientId: settings?.googleDriveClientId,
|
||||||
apiKey: settings?.googleDriveApiKey,
|
apiKey: settings?.googleDriveApiKey,
|
||||||
appId: settings?.googleDriveAppId,
|
appId: settings?.googleDriveAppId,
|
||||||
};
|
};
|
||||||
|
return gd;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (provider.id === 'oauth2-generic') {
|
if (provider.id === 'oauth2-generic') {
|
||||||
// Generic OAuth2 settings are at the root oauth2 level
|
const generic: OAuth2GenericSettings = {
|
||||||
return {
|
|
||||||
enabled: settings?.oauth2?.enabled,
|
enabled: settings?.oauth2?.enabled,
|
||||||
provider: settings?.oauth2?.provider,
|
provider: settings?.oauth2?.provider,
|
||||||
issuer: settings?.oauth2?.issuer,
|
issuer: settings?.oauth2?.issuer,
|
||||||
@@ -367,6 +418,7 @@ export default function AdminConnectionsSection() {
|
|||||||
autoCreateUser: settings?.oauth2?.autoCreateUser,
|
autoCreateUser: settings?.oauth2?.autoCreateUser,
|
||||||
blockRegistration: settings?.oauth2?.blockRegistration,
|
blockRegistration: settings?.oauth2?.blockRegistration,
|
||||||
};
|
};
|
||||||
|
return generic;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Specific OAuth2 provider settings
|
// Specific OAuth2 provider settings
|
||||||
@@ -386,32 +438,35 @@ export default function AdminConnectionsSection() {
|
|||||||
const linkedProviders = allProviders.filter((p) => isProviderConfigured(p));
|
const linkedProviders = allProviders.filter((p) => isProviderConfigured(p));
|
||||||
const availableProviders = allProviders.filter((p) => !isProviderConfigured(p));
|
const availableProviders = allProviders.filter((p) => !isProviderConfigured(p));
|
||||||
|
|
||||||
const updateProviderSettings = (provider: Provider, updatedSettings: Record<string, any>) => {
|
const updateProviderSettings = (provider: Provider, updatedSettings: Record<string, unknown>) => {
|
||||||
if (provider.id === 'smtp') {
|
if (provider.id === 'smtp') {
|
||||||
setSettings({ ...settings, mail: updatedSettings });
|
setSettings({ ...settings, mail: updatedSettings as MailSettings });
|
||||||
} else if (provider.id === 'telegram') {
|
} else if (provider.id === 'telegram') {
|
||||||
setSettings({ ...settings, telegram: updatedSettings });
|
setSettings({ ...settings, telegram: updatedSettings as TelegramSettingsData });
|
||||||
} else if (provider.id === 'googledrive') {
|
} else if (provider.id === 'googledrive') {
|
||||||
|
const gd = updatedSettings as GoogleDriveSettings;
|
||||||
setSettings({
|
setSettings({
|
||||||
...settings,
|
...settings,
|
||||||
googleDriveEnabled: updatedSettings.enabled,
|
googleDriveEnabled: gd.enabled,
|
||||||
googleDriveClientId: updatedSettings.clientId,
|
googleDriveClientId: gd.clientId,
|
||||||
googleDriveApiKey: updatedSettings.apiKey,
|
googleDriveApiKey: gd.apiKey,
|
||||||
googleDriveAppId: updatedSettings.appId,
|
googleDriveAppId: gd.appId,
|
||||||
});
|
});
|
||||||
} else if (provider.id === 'saml2') {
|
} else if (provider.id === 'saml2') {
|
||||||
setSettings({ ...settings, saml2: updatedSettings });
|
setSettings({ ...settings, saml2: updatedSettings as Saml2Settings });
|
||||||
} else if (provider.id === 'oauth2-generic') {
|
} else if (provider.id === 'oauth2-generic') {
|
||||||
setSettings({ ...settings, oauth2: updatedSettings });
|
const generic = updatedSettings as OAuth2GenericSettings;
|
||||||
|
setSettings({ ...settings, oauth2: { ...settings.oauth2, ...generic } });
|
||||||
} else {
|
} else {
|
||||||
// Specific OAuth2 provider
|
// Specific OAuth2 provider
|
||||||
|
const clientSettings = updatedSettings as OAuth2ClientSettings;
|
||||||
setSettings({
|
setSettings({
|
||||||
...settings,
|
...settings,
|
||||||
oauth2: {
|
oauth2: {
|
||||||
...settings.oauth2,
|
...settings.oauth2,
|
||||||
client: {
|
client: {
|
||||||
...settings.oauth2?.client,
|
...settings.oauth2?.client,
|
||||||
[provider.id]: updatedSettings
|
[provider.id]: clientSettings,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
+16
-15
@@ -1,4 +1,5 @@
|
|||||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
|
import { isAxiosError } from "axios";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import {
|
import {
|
||||||
NumberInput,
|
NumberInput,
|
||||||
@@ -68,7 +69,7 @@ export default function AdminDatabaseSection() {
|
|||||||
const { settings, setSettings, loading, saving, fetchSettings, saveSettings, isFieldPending } =
|
const { settings, setSettings, loading, saving, fetchSettings, saveSettings, isFieldPending } =
|
||||||
useAdminSettings<DatabaseSettingsData>({
|
useAdminSettings<DatabaseSettingsData>({
|
||||||
sectionName: "database",
|
sectionName: "database",
|
||||||
fetchTransformer: async (): Promise<DatabaseSettingsData & { _pending?: Record<string, any> }> => {
|
fetchTransformer: async (): Promise<DatabaseSettingsData & { _pending?: Record<string, unknown> }> => {
|
||||||
const response = await apiClient.get("/api/v1/admin/settings/section/system");
|
const response = await apiClient.get("/api/v1/admin/settings/section/system");
|
||||||
const systemData = response.data || {};
|
const systemData = response.data || {};
|
||||||
|
|
||||||
@@ -85,7 +86,7 @@ export default function AdminDatabaseSection() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Map pending changes from system._pending.datasource to root level
|
// Map pending changes from system._pending.datasource to root level
|
||||||
const result: DatabaseSettingsData & { _pending?: Record<string, any> } = { ...datasource };
|
const result: DatabaseSettingsData & { _pending?: Record<string, unknown> } = { ...datasource };
|
||||||
if (systemData._pending?.datasource) {
|
if (systemData._pending?.datasource) {
|
||||||
result._pending = systemData._pending.datasource;
|
result._pending = systemData._pending.datasource;
|
||||||
}
|
}
|
||||||
@@ -94,7 +95,7 @@ export default function AdminDatabaseSection() {
|
|||||||
},
|
},
|
||||||
saveTransformer: (settings: DatabaseSettingsData) => {
|
saveTransformer: (settings: DatabaseSettingsData) => {
|
||||||
// Convert flat settings to dot-notation for delta endpoint
|
// Convert flat settings to dot-notation for delta endpoint
|
||||||
const deltaSettings: Record<string, any> = {
|
const deltaSettings: Record<string, unknown> = {
|
||||||
"system.datasource.enableCustomDatabase": settings.enableCustomDatabase,
|
"system.datasource.enableCustomDatabase": settings.enableCustomDatabase,
|
||||||
"system.datasource.customDatabaseUrl": settings.customDatabaseUrl,
|
"system.datasource.customDatabaseUrl": settings.customDatabaseUrl,
|
||||||
"system.datasource.username": settings.username,
|
"system.datasource.username": settings.username,
|
||||||
@@ -141,8 +142,8 @@ export default function AdminDatabaseSection() {
|
|||||||
const data = await databaseManagementService.getDatabaseData();
|
const data = await databaseManagementService.getDatabaseData();
|
||||||
setBackupFiles(data.backupFiles || []);
|
setBackupFiles(data.backupFiles || []);
|
||||||
setDatabaseVersion(data.databaseVersion || null);
|
setDatabaseVersion(data.databaseVersion || null);
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
const message = error?.response?.data?.message || error?.message;
|
const message = isAxiosError(error) ? (error.response?.data?.message || error.message) : undefined;
|
||||||
alert({
|
alert({
|
||||||
alertType: "error",
|
alertType: "error",
|
||||||
title: t("admin.settings.database.loadError", "Failed to load database backups"),
|
title: t("admin.settings.database.loadError", "Failed to load database backups"),
|
||||||
@@ -189,8 +190,8 @@ export default function AdminDatabaseSection() {
|
|||||||
await databaseManagementService.createBackup();
|
await databaseManagementService.createBackup();
|
||||||
alert({ alertType: "success", title: t("admin.settings.database.backupCreated", "Backup created successfully") });
|
alert({ alertType: "success", title: t("admin.settings.database.backupCreated", "Backup created successfully") });
|
||||||
await loadBackupData();
|
await loadBackupData();
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
const message = error?.response?.data?.message || error?.message;
|
const message = isAxiosError(error) ? (error.response?.data?.message || error.message) : undefined;
|
||||||
alert({
|
alert({
|
||||||
alertType: "error",
|
alertType: "error",
|
||||||
title: t("admin.settings.database.backupFailed", "Failed to create backup"),
|
title: t("admin.settings.database.backupFailed", "Failed to create backup"),
|
||||||
@@ -209,8 +210,8 @@ export default function AdminDatabaseSection() {
|
|||||||
alert({ alertType: "success", title: t("admin.settings.database.importSuccess", "Backup imported successfully") });
|
alert({ alertType: "success", title: t("admin.settings.database.importSuccess", "Backup imported successfully") });
|
||||||
setUploadFile(null);
|
setUploadFile(null);
|
||||||
await loadBackupData();
|
await loadBackupData();
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
const message = error?.response?.data?.message || error?.message;
|
const message = isAxiosError(error) ? (error.response?.data?.message || error.message) : undefined;
|
||||||
alert({
|
alert({
|
||||||
alertType: "error",
|
alertType: "error",
|
||||||
title: t("admin.settings.database.importFailed", "Failed to import backup"),
|
title: t("admin.settings.database.importFailed", "Failed to import backup"),
|
||||||
@@ -270,8 +271,8 @@ export default function AdminDatabaseSection() {
|
|||||||
await databaseManagementService.importFromFileName(fileName);
|
await databaseManagementService.importFromFileName(fileName);
|
||||||
alert({ alertType: "success", title: t("admin.settings.database.importSuccess", "Backup imported successfully") });
|
alert({ alertType: "success", title: t("admin.settings.database.importSuccess", "Backup imported successfully") });
|
||||||
await loadBackupData();
|
await loadBackupData();
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
const message = error?.response?.data?.message || error?.message;
|
const message = isAxiosError(error) ? (error.response?.data?.message || error.message) : undefined;
|
||||||
alert({
|
alert({
|
||||||
alertType: "error",
|
alertType: "error",
|
||||||
title: t("admin.settings.database.importFailed", "Failed to import backup"),
|
title: t("admin.settings.database.importFailed", "Failed to import backup"),
|
||||||
@@ -289,8 +290,8 @@ export default function AdminDatabaseSection() {
|
|||||||
await databaseManagementService.deleteBackup(fileName);
|
await databaseManagementService.deleteBackup(fileName);
|
||||||
alert({ alertType: "success", title: t("admin.settings.database.deleteSuccess", "Backup deleted") });
|
alert({ alertType: "success", title: t("admin.settings.database.deleteSuccess", "Backup deleted") });
|
||||||
await loadBackupData();
|
await loadBackupData();
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
const message = error?.response?.data?.message || error?.message;
|
const message = isAxiosError(error) ? (error.response?.data?.message || error.message) : undefined;
|
||||||
alert({
|
alert({
|
||||||
alertType: "error",
|
alertType: "error",
|
||||||
title: t("admin.settings.database.deleteFailed", "Failed to delete backup"),
|
title: t("admin.settings.database.deleteFailed", "Failed to delete backup"),
|
||||||
@@ -320,8 +321,8 @@ export default function AdminDatabaseSection() {
|
|||||||
link.download = fileName;
|
link.download = fileName;
|
||||||
document.body.appendChild(link);
|
document.body.appendChild(link);
|
||||||
link.click();
|
link.click();
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
const message = error?.response?.data?.message || error?.message;
|
const message = isAxiosError(error) ? (error.response?.data?.message || error.message) : undefined;
|
||||||
alert({
|
alert({
|
||||||
alertType: "error",
|
alertType: "error",
|
||||||
title: t("admin.settings.database.downloadFailed", "Failed to download backup"),
|
title: t("admin.settings.database.downloadFailed", "Failed to download backup"),
|
||||||
|
|||||||
+3
-3
@@ -38,11 +38,11 @@ export default function AdminFeaturesSection() {
|
|||||||
isFieldPending,
|
isFieldPending,
|
||||||
} = useAdminSettings<FeaturesSettingsData>({
|
} = useAdminSettings<FeaturesSettingsData>({
|
||||||
sectionName: 'features',
|
sectionName: 'features',
|
||||||
fetchTransformer: async (): Promise<FeaturesSettingsData & { _pending?: Record<string, any> }> => {
|
fetchTransformer: async (): Promise<FeaturesSettingsData & { _pending?: Record<string, unknown> }> => {
|
||||||
const systemResponse = await apiClient.get('/api/v1/admin/settings/section/system');
|
const systemResponse = await apiClient.get('/api/v1/admin/settings/section/system');
|
||||||
const systemData = systemResponse.data || {};
|
const systemData = systemResponse.data || {};
|
||||||
|
|
||||||
const result: FeaturesSettingsData & { _pending?: Record<string, any> } = {
|
const result: FeaturesSettingsData & { _pending?: Record<string, unknown> } = {
|
||||||
serverCertificate: systemData.serverCertificate || {
|
serverCertificate: systemData.serverCertificate || {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
organizationName: 'Stirling-PDF',
|
organizationName: 'Stirling-PDF',
|
||||||
@@ -59,7 +59,7 @@ export default function AdminFeaturesSection() {
|
|||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
saveTransformer: (settings: FeaturesSettingsData) => {
|
saveTransformer: (settings: FeaturesSettingsData) => {
|
||||||
const deltaSettings: Record<string, any> = {};
|
const deltaSettings: Record<string, unknown> = {};
|
||||||
|
|
||||||
if (settings.serverCertificate) {
|
if (settings.serverCertificate) {
|
||||||
deltaSettings['system.serverCertificate.enabled'] = settings.serverCertificate.enabled;
|
deltaSettings['system.serverCertificate.enabled'] = settings.serverCertificate.enabled;
|
||||||
|
|||||||
+4
-4
@@ -91,7 +91,7 @@ export default function AdminGeneralSection() {
|
|||||||
isFieldPending,
|
isFieldPending,
|
||||||
} = useAdminSettings<GeneralSettingsData>({
|
} = useAdminSettings<GeneralSettingsData>({
|
||||||
sectionName: 'general',
|
sectionName: 'general',
|
||||||
fetchTransformer: async (): Promise<GeneralSettingsData & { _pending?: Record<string, any> }> => {
|
fetchTransformer: async (): Promise<GeneralSettingsData & { _pending?: Record<string, unknown> }> => {
|
||||||
const [uiResponse, systemResponse, premiumResponse] = await Promise.all([
|
const [uiResponse, systemResponse, premiumResponse] = await Promise.all([
|
||||||
apiClient.get('/api/v1/admin/settings/section/ui'),
|
apiClient.get('/api/v1/admin/settings/section/ui'),
|
||||||
apiClient.get('/api/v1/admin/settings/section/system'),
|
apiClient.get('/api/v1/admin/settings/section/system'),
|
||||||
@@ -113,7 +113,7 @@ export default function AdminGeneralSection() {
|
|||||||
? watchedFoldersDirs
|
? watchedFoldersDirs
|
||||||
: (pipelinePaths.watchedFoldersDir ? [pipelinePaths.watchedFoldersDir] : []);
|
: (pipelinePaths.watchedFoldersDir ? [pipelinePaths.watchedFoldersDir] : []);
|
||||||
|
|
||||||
const result: GeneralSettingsData & { _pending?: Record<string, any> } = {
|
const result: GeneralSettingsData & { _pending?: Record<string, unknown> } = {
|
||||||
ui,
|
ui,
|
||||||
system,
|
system,
|
||||||
customPaths: {
|
customPaths: {
|
||||||
@@ -140,7 +140,7 @@ export default function AdminGeneralSection() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Merge pending blocks from all three endpoints
|
// Merge pending blocks from all three endpoints
|
||||||
const pendingBlock: Record<string, any> = {};
|
const pendingBlock: Record<string, unknown> = {};
|
||||||
if (ui._pending) {
|
if (ui._pending) {
|
||||||
pendingBlock.ui = ui._pending;
|
pendingBlock.ui = ui._pending;
|
||||||
}
|
}
|
||||||
@@ -161,7 +161,7 @@ export default function AdminGeneralSection() {
|
|||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
saveTransformer: (settings: GeneralSettingsData) => {
|
saveTransformer: (settings: GeneralSettingsData) => {
|
||||||
const deltaSettings: Record<string, any> = {
|
const deltaSettings: Record<string, unknown> = {
|
||||||
// UI settings
|
// UI settings
|
||||||
'ui.appNameNavbar': settings.ui?.appNameNavbar,
|
'ui.appNameNavbar': settings.ui?.appNameNavbar,
|
||||||
'ui.languages': settings.ui?.languages,
|
'ui.languages': settings.ui?.languages,
|
||||||
|
|||||||
+2
-2
@@ -72,12 +72,12 @@ const AdminPlanSection: React.FC = () => {
|
|||||||
|
|
||||||
// Open billing portal in new tab
|
// Open billing portal in new tab
|
||||||
window.open(response.url, '_blank');
|
window.open(response.url, '_blank');
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
console.error('Failed to open billing portal:', error);
|
console.error('Failed to open billing portal:', error);
|
||||||
alert({
|
alert({
|
||||||
alertType: 'error',
|
alertType: 'error',
|
||||||
title: t('billing.portal.error', 'Failed to open billing portal'),
|
title: t('billing.portal.error', 'Failed to open billing portal'),
|
||||||
body: error.message || 'Please try again or contact support.',
|
body: (error instanceof Error ? error.message : undefined) || 'Please try again or contact support.',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [licenseInfo, t, validateLoginEnabled]);
|
}, [licenseInfo, t, validateLoginEnabled]);
|
||||||
|
|||||||
+3
-3
@@ -33,7 +33,7 @@ export default function AdminPrivacySection() {
|
|||||||
isFieldPending,
|
isFieldPending,
|
||||||
} = useAdminSettings<PrivacySettingsData>({
|
} = useAdminSettings<PrivacySettingsData>({
|
||||||
sectionName: 'privacy',
|
sectionName: 'privacy',
|
||||||
fetchTransformer: async (): Promise<PrivacySettingsData & { _pending?: Record<string, any> }> => {
|
fetchTransformer: async (): Promise<PrivacySettingsData & { _pending?: Record<string, unknown> }> => {
|
||||||
const [metricsResponse, systemResponse] = await Promise.all([
|
const [metricsResponse, systemResponse] = await Promise.all([
|
||||||
apiClient.get('/api/v1/admin/settings/section/metrics'),
|
apiClient.get('/api/v1/admin/settings/section/metrics'),
|
||||||
apiClient.get('/api/v1/admin/settings/section/system')
|
apiClient.get('/api/v1/admin/settings/section/system')
|
||||||
@@ -42,14 +42,14 @@ export default function AdminPrivacySection() {
|
|||||||
const metrics = metricsResponse.data;
|
const metrics = metricsResponse.data;
|
||||||
const system = systemResponse.data;
|
const system = systemResponse.data;
|
||||||
|
|
||||||
const result: PrivacySettingsData & { _pending?: Record<string, any> } = {
|
const result: PrivacySettingsData & { _pending?: Record<string, unknown> } = {
|
||||||
enableAnalytics: system.enableAnalytics || false,
|
enableAnalytics: system.enableAnalytics || false,
|
||||||
googleVisibility: system.googlevisibility || false,
|
googleVisibility: system.googlevisibility || false,
|
||||||
metricsEnabled: metrics.enabled || false
|
metricsEnabled: metrics.enabled || false
|
||||||
};
|
};
|
||||||
|
|
||||||
// Merge pending blocks from both endpoints
|
// Merge pending blocks from both endpoints
|
||||||
const pendingBlock: Record<string, any> = {};
|
const pendingBlock: Record<string, unknown> = {};
|
||||||
if (system._pending?.enableAnalytics !== undefined) {
|
if (system._pending?.enableAnalytics !== undefined) {
|
||||||
pendingBlock.enableAnalytics = system._pending.enableAnalytics;
|
pendingBlock.enableAnalytics = system._pending.enableAnalytics;
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -67,7 +67,7 @@ export default function AdminSecuritySection() {
|
|||||||
isFieldPending,
|
isFieldPending,
|
||||||
} = useAdminSettings<SecuritySettingsData>({
|
} = useAdminSettings<SecuritySettingsData>({
|
||||||
sectionName: 'security',
|
sectionName: 'security',
|
||||||
fetchTransformer: async (): Promise<SecuritySettingsData & { _pending?: Record<string, any> }> => {
|
fetchTransformer: async (): Promise<SecuritySettingsData & { _pending?: Record<string, unknown> }> => {
|
||||||
const [securityResponse, premiumResponse, systemResponse] = await Promise.all([
|
const [securityResponse, premiumResponse, systemResponse] = await Promise.all([
|
||||||
apiClient.get('/api/v1/admin/settings/section/security'),
|
apiClient.get('/api/v1/admin/settings/section/security'),
|
||||||
apiClient.get('/api/v1/admin/settings/section/premium'),
|
apiClient.get('/api/v1/admin/settings/section/premium'),
|
||||||
@@ -93,7 +93,7 @@ export default function AdminSecuritySection() {
|
|||||||
systemPending: JSON.parse(JSON.stringify(systemPending || {}))
|
systemPending: JSON.parse(JSON.stringify(systemPending || {}))
|
||||||
});
|
});
|
||||||
|
|
||||||
const combined: SecuritySettingsData & { _pending?: Record<string, any> } = {
|
const combined: SecuritySettingsData & { _pending?: Record<string, unknown> } = {
|
||||||
...securityActive
|
...securityActive
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -108,7 +108,7 @@ export default function AdminSecuritySection() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Merge all _pending blocks
|
// Merge all _pending blocks
|
||||||
const mergedPending: Record<string, any> = {};
|
const mergedPending: Record<string, unknown> = {};
|
||||||
if (securityPending) {
|
if (securityPending) {
|
||||||
Object.assign(mergedPending, securityPending);
|
Object.assign(mergedPending, securityPending);
|
||||||
}
|
}
|
||||||
@@ -128,7 +128,7 @@ export default function AdminSecuritySection() {
|
|||||||
saveTransformer: (settings: SecuritySettingsData) => {
|
saveTransformer: (settings: SecuritySettingsData) => {
|
||||||
const { audit, html, ...securitySettings } = settings;
|
const { audit, html, ...securitySettings } = settings;
|
||||||
|
|
||||||
const deltaSettings: Record<string, any> = {
|
const deltaSettings: Record<string, unknown> = {
|
||||||
// Security settings
|
// Security settings
|
||||||
'security.enableLogin': securitySettings.enableLogin,
|
'security.enableLogin': securitySettings.enableLogin,
|
||||||
'security.loginMethod': securitySettings.loginMethod,
|
'security.loginMethod': securitySettings.loginMethod,
|
||||||
|
|||||||
+2
-2
@@ -155,10 +155,10 @@ const AdminUsageSection: React.FC = () => {
|
|||||||
|
|
||||||
const displayedVisits = endpoints.reduce((sum, e) => sum + e.visits, 0);
|
const displayedVisits = endpoints.reduce((sum, e) => sum + e.visits, 0);
|
||||||
const totalVisits = Number.isFinite(data?.totalVisits)
|
const totalVisits = Number.isFinite(data?.totalVisits)
|
||||||
? Math.max(0, data?.totalVisits as number)
|
? Math.max(0, data?.totalVisits ?? 0)
|
||||||
: displayedVisits;
|
: displayedVisits;
|
||||||
const totalEndpoints = Number.isFinite(data?.totalEndpoints)
|
const totalEndpoints = Number.isFinite(data?.totalEndpoints)
|
||||||
? Math.max(0, data?.totalEndpoints as number)
|
? Math.max(0, data?.totalEndpoints ?? 0)
|
||||||
: endpoints.length;
|
: endpoints.length;
|
||||||
|
|
||||||
const displayedPercentage = totalVisits > 0
|
const displayedPercentage = totalVisits > 0
|
||||||
|
|||||||
+27
-28
@@ -1,4 +1,5 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
|
import { isAxiosError } from 'axios';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
Stack,
|
Stack,
|
||||||
@@ -17,6 +18,7 @@ import {
|
|||||||
CloseButton,
|
CloseButton,
|
||||||
Avatar,
|
Avatar,
|
||||||
Box,
|
Box,
|
||||||
|
type ComboboxItem,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import LocalIcon from '@app/components/shared/LocalIcon';
|
import LocalIcon from '@app/components/shared/LocalIcon';
|
||||||
import { alert } from '@app/components/toast';
|
import { alert } from '@app/components/toast';
|
||||||
@@ -114,7 +116,7 @@ export default function PeopleSection() {
|
|||||||
...user,
|
...user,
|
||||||
isActive: adminData.userSessions[user.username] || false,
|
isActive: adminData.userSessions[user.username] || false,
|
||||||
lastRequest: adminData.userLastRequest[user.username] || undefined,
|
lastRequest: adminData.userLastRequest[user.username] || undefined,
|
||||||
mfaEnabled: adminData.userSettings?.[user.username]?.mfaEnabled === 'true',
|
mfaEnabled: (adminData.userSettings?.[user.username] as Record<string, unknown> | undefined)?.mfaEnabled === 'true',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
setUsers(enrichedUsers);
|
setUsers(enrichedUsers);
|
||||||
@@ -225,12 +227,11 @@ export default function PeopleSection() {
|
|||||||
alert({ alertType: 'success', title: t('workspace.people.editMember.success') });
|
alert({ alertType: 'success', title: t('workspace.people.editMember.success') });
|
||||||
closeEditModal();
|
closeEditModal();
|
||||||
fetchData();
|
fetchData();
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
console.error('[PeopleSection] Failed to update user:', error);
|
console.error('[PeopleSection] Failed to update user:', error);
|
||||||
const errorMessage = error.response?.data?.message ||
|
const errorMessage = isAxiosError(error)
|
||||||
error.response?.data?.error ||
|
? (error.response?.data?.message || error.response?.data?.error || error.message)
|
||||||
error.message ||
|
: (error instanceof Error ? error.message : undefined) || t('workspace.people.editMember.error');
|
||||||
t('workspace.people.editMember.error');
|
|
||||||
alert({ alertType: 'error', title: errorMessage });
|
alert({ alertType: 'error', title: errorMessage });
|
||||||
} finally {
|
} finally {
|
||||||
setProcessing(false);
|
setProcessing(false);
|
||||||
@@ -242,12 +243,11 @@ export default function PeopleSection() {
|
|||||||
await userManagementService.toggleUserEnabled(user.username, !user.enabled);
|
await userManagementService.toggleUserEnabled(user.username, !user.enabled);
|
||||||
alert({ alertType: 'success', title: t('workspace.people.toggleEnabled.success') });
|
alert({ alertType: 'success', title: t('workspace.people.toggleEnabled.success') });
|
||||||
fetchData();
|
fetchData();
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
console.error('[PeopleSection] Failed to toggle user status:', error);
|
console.error('[PeopleSection] Failed to toggle user status:', error);
|
||||||
const errorMessage = error.response?.data?.message ||
|
const errorMessage = isAxiosError(error)
|
||||||
error.response?.data?.error ||
|
? (error.response?.data?.message || error.response?.data?.error || error.message)
|
||||||
error.message ||
|
: (error instanceof Error ? error.message : undefined) || t('workspace.people.toggleEnabled.error');
|
||||||
t('workspace.people.toggleEnabled.error');
|
|
||||||
alert({ alertType: 'error', title: errorMessage });
|
alert({ alertType: 'error', title: errorMessage });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -262,12 +262,12 @@ export default function PeopleSection() {
|
|||||||
await userManagementService.deleteUser(user.username);
|
await userManagementService.deleteUser(user.username);
|
||||||
alert({ alertType: 'success', title: t('workspace.people.deleteUserSuccess', 'User deleted successfully') });
|
alert({ alertType: 'success', title: t('workspace.people.deleteUserSuccess', 'User deleted successfully') });
|
||||||
fetchData();
|
fetchData();
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
console.error('[PeopleSection] Failed to delete user:', error);
|
console.error('[PeopleSection] Failed to delete user:', error);
|
||||||
const errorMessage = error.response?.data?.message ||
|
const errorMessage = isAxiosError(error)
|
||||||
error.response?.data?.error ||
|
? (error.response?.data?.message || error.response?.data?.error || error.message)
|
||||||
error.message ||
|
: (error instanceof Error ? error.message : undefined) ||
|
||||||
t('workspace.people.deleteUserError', 'Failed to delete user');
|
t('workspace.people.deleteUserError', 'Failed to delete user');
|
||||||
alert({ alertType: 'error', title: errorMessage });
|
alert({ alertType: 'error', title: errorMessage });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -282,12 +282,11 @@ export default function PeopleSection() {
|
|||||||
await userManagementService.unlockUser(user.username);
|
await userManagementService.unlockUser(user.username);
|
||||||
alert({ alertType: 'success', title: t('workspace.people.unlockUserSuccess', 'User account unlocked successfully') });
|
alert({ alertType: 'success', title: t('workspace.people.unlockUserSuccess', 'User account unlocked successfully') });
|
||||||
fetchData();
|
fetchData();
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
console.error('[PeopleSection] Failed to unlock user:', error);
|
console.error('[PeopleSection] Failed to unlock user:', error);
|
||||||
const errorMessage = error.response?.data?.message ||
|
const errorMessage = isAxiosError(error)
|
||||||
error.response?.data?.error ||
|
? (error.response?.data?.message || error.response?.data?.error || error.message)
|
||||||
error.message ||
|
: (error instanceof Error ? error.message : undefined) || t('workspace.people.unlockUserError', 'Failed to unlock user account');
|
||||||
t('workspace.people.unlockUserError', 'Failed to unlock user account');
|
|
||||||
alert({ alertType: 'error', title: errorMessage });
|
alert({ alertType: 'error', title: errorMessage });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -339,9 +338,9 @@ export default function PeopleSection() {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const renderRoleOption = ({ option }: { option: any }) => (
|
const renderRoleOption = ({ option }: { option: ComboboxItem & { icon?: string; description?: string } }) => (
|
||||||
<Group gap="sm" wrap="nowrap">
|
<Group gap="sm" wrap="nowrap">
|
||||||
<LocalIcon icon={option.icon} width="1.25rem" height="1.25rem" style={{ flexShrink: 0 }} />
|
<LocalIcon icon={option.icon ?? ''} width="1.25rem" height="1.25rem" style={{ flexShrink: 0 }} />
|
||||||
<Box style={{ flex: 1 }}>
|
<Box style={{ flex: 1 }}>
|
||||||
<Text size="sm" fw={500}>{option.label}</Text>
|
<Text size="sm" fw={500}>{option.label}</Text>
|
||||||
<Text size="xs" c="dimmed" style={{ whiteSpace: 'normal', lineHeight: 1.4 }}>
|
<Text size="xs" c="dimmed" style={{ whiteSpace: 'normal', lineHeight: 1.4 }}>
|
||||||
@@ -668,12 +667,12 @@ export default function PeopleSection() {
|
|||||||
try {
|
try {
|
||||||
await userManagementService.disableMfaByAdmin(user.username);
|
await userManagementService.disableMfaByAdmin(user.username);
|
||||||
alert({ alertType: 'success', title: t('workspace.people.mfa.adminDisableSuccess', 'MFA disabled successfully for user') });
|
alert({ alertType: 'success', title: t('workspace.people.mfa.adminDisableSuccess', 'MFA disabled successfully for user') });
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
console.error('[PeopleSection] Failed to disable MFA for user:', error);
|
console.error('[PeopleSection] Failed to disable MFA for user:', error);
|
||||||
const errorMessage = error.response?.data?.message ||
|
const errorMessage = isAxiosError(error)
|
||||||
error.response?.data?.error ||
|
? (error.response?.data?.message || error.response?.data?.error || error.message)
|
||||||
error.message ||
|
: (error instanceof Error ? error.message : undefined) ||
|
||||||
t('workspace.people.mfa.adminDisableError', 'Failed to disable MFA for user');
|
t('workspace.people.mfa.adminDisableError', 'Failed to disable MFA for user');
|
||||||
alert({ alertType: 'error', title: errorMessage });
|
alert({ alertType: 'error', title: errorMessage });
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|||||||
+23
-25
@@ -1,4 +1,5 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
|
import { isAxiosError } from 'axios';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
Stack,
|
Stack,
|
||||||
@@ -110,12 +111,11 @@ export default function TeamDetailsSection({ teamId, onBack }: TeamDetailsSectio
|
|||||||
setAddMemberModalOpened(false);
|
setAddMemberModalOpened(false);
|
||||||
setSelectedUserId('');
|
setSelectedUserId('');
|
||||||
fetchTeamDetails();
|
fetchTeamDetails();
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
console.error('Failed to add member:', error);
|
console.error('Failed to add member:', error);
|
||||||
const errorMessage = error.response?.data?.message ||
|
const errorMessage = isAxiosError(error)
|
||||||
error.response?.data?.error ||
|
? (error.response?.data?.message || error.response?.data?.error || error.message)
|
||||||
error.message ||
|
: (error instanceof Error ? error.message : undefined) || t('workspace.teams.addMemberToTeam.error', 'Failed to add user to team');
|
||||||
t('workspace.teams.addMemberToTeam.error', 'Failed to add user to team');
|
|
||||||
alert({ alertType: 'error', title: errorMessage });
|
alert({ alertType: 'error', title: errorMessage });
|
||||||
} finally {
|
} finally {
|
||||||
setProcessing(false);
|
setProcessing(false);
|
||||||
@@ -140,12 +140,11 @@ export default function TeamDetailsSection({ teamId, onBack }: TeamDetailsSectio
|
|||||||
await teamService.moveUserToTeam(user.username, user.rolesAsString || 'ROLE_USER', defaultTeam.id);
|
await teamService.moveUserToTeam(user.username, user.rolesAsString || 'ROLE_USER', defaultTeam.id);
|
||||||
alert({ alertType: 'success', title: t('workspace.teams.removeMemberSuccess', 'User removed from team') });
|
alert({ alertType: 'success', title: t('workspace.teams.removeMemberSuccess', 'User removed from team') });
|
||||||
fetchTeamDetails();
|
fetchTeamDetails();
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
console.error('Failed to remove member:', error);
|
console.error('Failed to remove member:', error);
|
||||||
const errorMessage = error.response?.data?.message ||
|
const errorMessage = isAxiosError(error)
|
||||||
error.response?.data?.error ||
|
? (error.response?.data?.message || error.response?.data?.error || error.message)
|
||||||
error.message ||
|
: (error instanceof Error ? error.message : undefined) || t('workspace.teams.removeMemberError', 'Failed to remove user from team');
|
||||||
t('workspace.teams.removeMemberError', 'Failed to remove user from team');
|
|
||||||
alert({ alertType: 'error', title: errorMessage });
|
alert({ alertType: 'error', title: errorMessage });
|
||||||
} finally {
|
} finally {
|
||||||
setProcessing(false);
|
setProcessing(false);
|
||||||
@@ -163,12 +162,12 @@ export default function TeamDetailsSection({ teamId, onBack }: TeamDetailsSectio
|
|||||||
await userManagementService.deleteUser(user.username);
|
await userManagementService.deleteUser(user.username);
|
||||||
alert({ alertType: 'success', title: t('workspace.people.deleteUserSuccess', 'User deleted successfully') });
|
alert({ alertType: 'success', title: t('workspace.people.deleteUserSuccess', 'User deleted successfully') });
|
||||||
fetchTeamDetails();
|
fetchTeamDetails();
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
console.error('Failed to delete user:', error);
|
console.error('Failed to delete user:', error);
|
||||||
const errorMessage = error.response?.data?.message ||
|
const errorMessage = isAxiosError(error)
|
||||||
error.response?.data?.error ||
|
? (error.response?.data?.message || error.response?.data?.error || error.message)
|
||||||
error.message ||
|
: (error instanceof Error ? error.message : undefined) ||
|
||||||
t('workspace.people.deleteUserError', 'Failed to delete user');
|
t('workspace.people.deleteUserError', 'Failed to delete user');
|
||||||
alert({ alertType: 'error', title: errorMessage });
|
alert({ alertType: 'error', title: errorMessage });
|
||||||
} finally {
|
} finally {
|
||||||
setProcessing(false);
|
setProcessing(false);
|
||||||
@@ -185,12 +184,11 @@ export default function TeamDetailsSection({ teamId, onBack }: TeamDetailsSectio
|
|||||||
await userManagementService.unlockUser(user.username);
|
await userManagementService.unlockUser(user.username);
|
||||||
alert({ alertType: 'success', title: t('workspace.people.unlockUserSuccess', 'User account unlocked successfully') });
|
alert({ alertType: 'success', title: t('workspace.people.unlockUserSuccess', 'User account unlocked successfully') });
|
||||||
fetchTeamDetails();
|
fetchTeamDetails();
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
console.error('[TeamDetailsSection] Failed to unlock user:', error);
|
console.error('[TeamDetailsSection] Failed to unlock user:', error);
|
||||||
const errorMessage = error.response?.data?.message ||
|
const errorMessage = isAxiosError(error)
|
||||||
error.response?.data?.error ||
|
? (error.response?.data?.message || error.response?.data?.error || error.message)
|
||||||
error.message ||
|
: (error instanceof Error ? error.message : undefined) || t('workspace.people.unlockUserError', 'Failed to unlock user account');
|
||||||
t('workspace.people.unlockUserError', 'Failed to unlock user account');
|
|
||||||
alert({ alertType: 'error', title: errorMessage });
|
alert({ alertType: 'error', title: errorMessage });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -225,12 +223,12 @@ export default function TeamDetailsSection({ teamId, onBack }: TeamDetailsSectio
|
|||||||
setSelectedUser(null);
|
setSelectedUser(null);
|
||||||
setSelectedTeamId('');
|
setSelectedTeamId('');
|
||||||
fetchTeamDetails();
|
fetchTeamDetails();
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
console.error('Failed to change team:', error);
|
console.error('Failed to change team:', error);
|
||||||
const errorMessage = error.response?.data?.message ||
|
const errorMessage = isAxiosError(error)
|
||||||
error.response?.data?.error ||
|
? (error.response?.data?.message || error.response?.data?.error || error.message)
|
||||||
error.message ||
|
: (error instanceof Error ? error.message : undefined) ||
|
||||||
t('workspace.teams.changeTeam.error', 'Failed to change team');
|
t('workspace.teams.changeTeam.error', 'Failed to change team');
|
||||||
alert({ alertType: 'error', title: errorMessage });
|
alert({ alertType: 'error', title: errorMessage });
|
||||||
} finally {
|
} finally {
|
||||||
setProcessing(false);
|
setProcessing(false);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
|
import { isAxiosError } from 'axios';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
Stack,
|
Stack,
|
||||||
@@ -83,12 +84,11 @@ export default function TeamsSection() {
|
|||||||
setNewTeamName('');
|
setNewTeamName('');
|
||||||
setCreateModalOpened(false);
|
setCreateModalOpened(false);
|
||||||
await fetchTeams();
|
await fetchTeams();
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
console.error('Failed to create team:', error);
|
console.error('Failed to create team:', error);
|
||||||
const errorMessage = error.response?.data?.message ||
|
const errorMessage = isAxiosError(error)
|
||||||
error.response?.data?.error ||
|
? (error.response?.data?.message || error.response?.data?.error || error.message)
|
||||||
error.message ||
|
: (error instanceof Error ? error.message : undefined) || t('workspace.teams.createTeam.error');
|
||||||
t('workspace.teams.createTeam.error');
|
|
||||||
alert({ alertType: 'error', title: errorMessage });
|
alert({ alertType: 'error', title: errorMessage });
|
||||||
} finally {
|
} finally {
|
||||||
setProcessing(false);
|
setProcessing(false);
|
||||||
@@ -109,12 +109,11 @@ export default function TeamsSection() {
|
|||||||
setSelectedTeam(null);
|
setSelectedTeam(null);
|
||||||
setRenameModalOpened(false);
|
setRenameModalOpened(false);
|
||||||
await fetchTeams();
|
await fetchTeams();
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
console.error('Failed to rename team:', error);
|
console.error('Failed to rename team:', error);
|
||||||
const errorMessage = error.response?.data?.message ||
|
const errorMessage = isAxiosError(error)
|
||||||
error.response?.data?.error ||
|
? (error.response?.data?.message || error.response?.data?.error || error.message)
|
||||||
error.message ||
|
: (error instanceof Error ? error.message : undefined) || t('workspace.teams.renameTeam.error');
|
||||||
t('workspace.teams.renameTeam.error');
|
|
||||||
alert({ alertType: 'error', title: errorMessage });
|
alert({ alertType: 'error', title: errorMessage });
|
||||||
} finally {
|
} finally {
|
||||||
setProcessing(false);
|
setProcessing(false);
|
||||||
@@ -135,12 +134,12 @@ export default function TeamsSection() {
|
|||||||
await teamService.deleteTeam(team.id);
|
await teamService.deleteTeam(team.id);
|
||||||
alert({ alertType: 'success', title: t('workspace.teams.deleteTeam.success') });
|
alert({ alertType: 'success', title: t('workspace.teams.deleteTeam.success') });
|
||||||
await fetchTeams();
|
await fetchTeams();
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
console.error('Failed to delete team:', error);
|
console.error('Failed to delete team:', error);
|
||||||
const errorMessage = error.response?.data?.message ||
|
const errorMessage = isAxiosError(error)
|
||||||
error.response?.data?.error ||
|
? (error.response?.data?.message || error.response?.data?.error || error.message)
|
||||||
error.message ||
|
: (error instanceof Error ? error.message : undefined) ||
|
||||||
t('workspace.teams.deleteTeam.error');
|
t('workspace.teams.deleteTeam.error');
|
||||||
alert({ alertType: 'error', title: errorMessage });
|
alert({ alertType: 'error', title: errorMessage });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
+9
-9
@@ -15,7 +15,7 @@ import {
|
|||||||
UnstyledButton,
|
UnstyledButton,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import auditService, { AuditEvent } from '@app/services/auditService';
|
import auditService, { AuditEvent, AuditFilters } from '@app/services/auditService';
|
||||||
import { Z_INDEX_OVER_CONFIG_MODAL } from '@app/styles/zIndex';
|
import { Z_INDEX_OVER_CONFIG_MODAL } from '@app/styles/zIndex';
|
||||||
import { useAuditFilters } from '@app/hooks/useAuditFilters';
|
import { useAuditFilters } from '@app/hooks/useAuditFilters';
|
||||||
import AuditFiltersForm from '@app/components/shared/config/configSections/audit/AuditFiltersForm';
|
import AuditFiltersForm from '@app/components/shared/config/configSections/audit/AuditFiltersForm';
|
||||||
@@ -122,7 +122,7 @@ const AuditEventsTable: React.FC<AuditEventsTableProps> = ({
|
|||||||
}, [filters, currentPage, loginEnabled]);
|
}, [filters, currentPage, loginEnabled]);
|
||||||
|
|
||||||
// Wrap filter handlers to reset pagination
|
// Wrap filter handlers to reset pagination
|
||||||
const handleFilterChangeWithReset = (key: keyof typeof filters, value: any) => {
|
const handleFilterChangeWithReset = (key: keyof AuditFilters, value: AuditFilters[keyof AuditFilters]) => {
|
||||||
handleFilterChange(key, value);
|
handleFilterChange(key, value);
|
||||||
setCurrentPage(1);
|
setCurrentPage(1);
|
||||||
};
|
};
|
||||||
@@ -170,8 +170,8 @@ const AuditEventsTable: React.FC<AuditEventsTableProps> = ({
|
|||||||
|
|
||||||
// Apply sorting to current events
|
// Apply sorting to current events
|
||||||
const sortedEvents = [...events].sort((a, b) => {
|
const sortedEvents = [...events].sort((a, b) => {
|
||||||
let aVal: any;
|
let aVal: string | number | undefined;
|
||||||
let bVal: any;
|
let bVal: string | number | undefined;
|
||||||
|
|
||||||
switch (sortKey) {
|
switch (sortKey) {
|
||||||
case 'timestamp':
|
case 'timestamp':
|
||||||
@@ -296,14 +296,14 @@ const AuditEventsTable: React.FC<AuditEventsTableProps> = ({
|
|||||||
let author = '';
|
let author = '';
|
||||||
let fileHash = '';
|
let fileHash = '';
|
||||||
if (event.details && typeof event.details === 'object') {
|
if (event.details && typeof event.details === 'object') {
|
||||||
const details = event.details as Record<string, any>;
|
const details = event.details as Record<string, unknown>;
|
||||||
const files = details.files;
|
const files = details.files;
|
||||||
if (Array.isArray(files) && files.length > 0) {
|
if (Array.isArray(files) && files.length > 0) {
|
||||||
const firstFile = files[0] as Record<string, any>;
|
const firstFile = files[0] as Record<string, unknown>;
|
||||||
documentName = firstFile.name || '';
|
documentName = typeof firstFile.name === 'string' ? firstFile.name : '';
|
||||||
if (showAuthor || showFileHash) {
|
if (showAuthor || showFileHash) {
|
||||||
author = firstFile.pdfAuthor || '';
|
author = typeof firstFile.pdfAuthor === 'string' ? firstFile.pdfAuthor : '';
|
||||||
fileHash = firstFile.fileHash ? firstFile.fileHash.substring(0, 16) + '...' : '';
|
fileHash = typeof firstFile.fileHash === 'string' ? firstFile.fileHash.substring(0, 16) + '...' : '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -51,7 +51,7 @@ const AuditExportSection: React.FC<AuditExportSectionProps> = ({
|
|||||||
try {
|
try {
|
||||||
setExporting(true);
|
setExporting(true);
|
||||||
|
|
||||||
const fieldsParam = Object.keys(selectedFields).filter(k => selectedFields[k as keyof typeof selectedFields]).join(',');
|
const fieldsParam = Object.keys(selectedFields).filter(k => selectedFields[k]).join(',');
|
||||||
|
|
||||||
const blob = await auditService.exportData(exportFormat, { ...filters, fields: fieldsParam });
|
const blob = await auditService.exportData(exportFormat, { ...filters, fields: fieldsParam });
|
||||||
|
|
||||||
|
|||||||
+5
-5
@@ -43,7 +43,7 @@ interface AuditFiltersFormProps {
|
|||||||
filters: AuditFilters;
|
filters: AuditFilters;
|
||||||
eventTypes: string[];
|
eventTypes: string[];
|
||||||
users: string[];
|
users: string[];
|
||||||
onFilterChange: (key: keyof AuditFilters, value: any) => void;
|
onFilterChange: (key: keyof AuditFilters, value: AuditFilters[keyof AuditFilters]) => void;
|
||||||
onClearFilters: () => void;
|
onClearFilters: () => void;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
@@ -147,8 +147,8 @@ const AuditFiltersForm: React.FC<AuditFiltersFormProps> = ({
|
|||||||
<DateInput
|
<DateInput
|
||||||
placeholder={t('audit.events.startDate', 'Start date')}
|
placeholder={t('audit.events.startDate', 'Start date')}
|
||||||
value={filters.startDate ? new Date(filters.startDate) : null}
|
value={filters.startDate ? new Date(filters.startDate) : null}
|
||||||
onChange={(value: any) => {
|
onChange={(value) => {
|
||||||
onFilterChange('startDate', value ? formatDateToYMD(value as Date) : undefined);
|
onFilterChange('startDate', value ? formatDateToYMD(new Date(value)) : undefined);
|
||||||
}}
|
}}
|
||||||
clearable
|
clearable
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
@@ -157,8 +157,8 @@ const AuditFiltersForm: React.FC<AuditFiltersFormProps> = ({
|
|||||||
<DateInput
|
<DateInput
|
||||||
placeholder={t('audit.events.endDate', 'End date')}
|
placeholder={t('audit.events.endDate', 'End date')}
|
||||||
value={filters.endDate ? new Date(filters.endDate) : null}
|
value={filters.endDate ? new Date(filters.endDate) : null}
|
||||||
onChange={(value: any) => {
|
onChange={(value) => {
|
||||||
onFilterChange('endDate', value ? formatDateToYMD(value as Date) : undefined);
|
onFilterChange('endDate', value ? formatDateToYMD(new Date(value)) : undefined);
|
||||||
}}
|
}}
|
||||||
clearable
|
clearable
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
|||||||
+2
-2
@@ -14,7 +14,7 @@ export const useLicensePolling = (
|
|||||||
const pollForLicenseKey = useCallback(async (installId: string) => {
|
const pollForLicenseKey = useCallback(async (installId: string) => {
|
||||||
// Use shared polling utility
|
// Use shared polling utility
|
||||||
const result = await pollLicenseKeyWithBackoff(installId, {
|
const result = await pollLicenseKeyWithBackoff(installId, {
|
||||||
isMounted: () => isMountedRef.current!,
|
isMounted: () => isMountedRef.current ?? false,
|
||||||
onStatusChange: setPollingStatus,
|
onStatusChange: setPollingStatus,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ export const useLicensePolling = (
|
|||||||
|
|
||||||
// Activate the license key
|
// Activate the license key
|
||||||
const activation = await activateLicenseKey(result.licenseKey, {
|
const activation = await activateLicenseKey(result.licenseKey, {
|
||||||
isMounted: () => isMountedRef.current!,
|
isMounted: () => isMountedRef.current ?? false,
|
||||||
onActivated: onLicenseActivated,
|
onActivated: onLicenseActivated,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -112,8 +112,8 @@ const ParticipantView: React.FC<ParticipantViewProps> = ({ token }) => {
|
|||||||
showLogo: true,
|
showLogo: true,
|
||||||
});
|
});
|
||||||
setNotification({ type: 'success', message: 'Signature submitted successfully!' });
|
setNotification({ type: 'success', message: 'Signature submitted successfully!' });
|
||||||
} catch (err: any) {
|
} catch (err: unknown) {
|
||||||
setNotification({ type: 'error', message: `Failed to submit signature: ${err.message}` });
|
setNotification({ type: 'error', message: `Failed to submit signature: ${err instanceof Error ? err.message : String(err)}` });
|
||||||
} finally {
|
} finally {
|
||||||
setIsSubmitting(false);
|
setIsSubmitting(false);
|
||||||
}
|
}
|
||||||
@@ -125,8 +125,8 @@ const ParticipantView: React.FC<ParticipantViewProps> = ({ token }) => {
|
|||||||
try {
|
try {
|
||||||
await decline(token, declineReason || 'Declined by participant');
|
await decline(token, declineReason || 'Declined by participant');
|
||||||
setNotification({ type: 'success', message: 'You have declined this signing request.' });
|
setNotification({ type: 'success', message: 'You have declined this signing request.' });
|
||||||
} catch (err: any) {
|
} catch (err: unknown) {
|
||||||
setNotification({ type: 'error', message: `Failed to decline: ${err.message}` });
|
setNotification({ type: 'error', message: `Failed to decline: ${err instanceof Error ? err.message : String(err)}` });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
type ReactNode,
|
type ReactNode,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import apiClient from '@app/services/apiClient';
|
import apiClient from '@app/services/apiClient';
|
||||||
|
import { isAxiosError } from 'axios';
|
||||||
import { useAppConfig } from '@app/contexts/AppConfigContext';
|
import { useAppConfig } from '@app/contexts/AppConfigContext';
|
||||||
import { useAuth } from '@app/auth/UseSession';
|
import { useAuth } from '@app/auth/UseSession';
|
||||||
import { useLicense } from '@app/contexts/LicenseContext';
|
import { useLicense } from '@app/contexts/LicenseContext';
|
||||||
@@ -95,13 +96,8 @@ function getErrorMessage(error: unknown): string {
|
|||||||
if (typeof error === 'string') {
|
if (typeof error === 'string') {
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
if (
|
if (isAxiosError(error) && typeof error.response?.data?.message === 'string') {
|
||||||
typeof error === 'object' &&
|
return error.response.data.message;
|
||||||
error !== null &&
|
|
||||||
'response' in error &&
|
|
||||||
typeof (error as any).response?.data?.message === 'string'
|
|
||||||
) {
|
|
||||||
return (error as any).response.data.message;
|
|
||||||
}
|
}
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
return error.message;
|
return error.message;
|
||||||
@@ -196,7 +192,7 @@ export function ServerExperienceProvider({ children }: { children: ReactNode })
|
|||||||
(
|
(
|
||||||
await apiClient.get<{ totalUsers?: number }>(
|
await apiClient.get<{ totalUsers?: number }>(
|
||||||
'/api/v1/proprietary/ui-data/admin-settings',
|
'/api/v1/proprietary/ui-data/admin-settings',
|
||||||
{ suppressErrorToast: true } as any,
|
{ suppressErrorToast: true },
|
||||||
)
|
)
|
||||||
).data;
|
).data;
|
||||||
const totalUsers =
|
const totalUsers =
|
||||||
@@ -219,7 +215,7 @@ export function ServerExperienceProvider({ children }: { children: ReactNode })
|
|||||||
(
|
(
|
||||||
await apiClient.get<WeeklyActiveUsersResponse>('/api/v1/info/wau', {
|
await apiClient.get<WeeklyActiveUsersResponse>('/api/v1/info/wau', {
|
||||||
suppressErrorToast: true,
|
suppressErrorToast: true,
|
||||||
} as any)
|
})
|
||||||
).data;
|
).data;
|
||||||
const weeklyActiveUsers =
|
const weeklyActiveUsers =
|
||||||
typeof responseData?.weeklyActiveUsers === 'number'
|
typeof responseData?.weeklyActiveUsers === 'number'
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useState, useCallback, useEffect } from 'react';
|
import { useState, useCallback, useEffect } from 'react';
|
||||||
|
import { isAxiosError } from 'axios';
|
||||||
import workflowService, {
|
import workflowService, {
|
||||||
WorkflowSessionResponse,
|
WorkflowSessionResponse,
|
||||||
ParticipantResponse,
|
ParticipantResponse,
|
||||||
@@ -35,9 +36,10 @@ export const useParticipantSession = (token?: string): UseParticipantSessionResu
|
|||||||
]);
|
]);
|
||||||
setSession(sessionData);
|
setSession(sessionData);
|
||||||
setParticipant(participantData);
|
setParticipant(participantData);
|
||||||
} catch (err: any) {
|
} catch (err: unknown) {
|
||||||
const errorMsg =
|
const errorMsg = isAxiosError(err)
|
||||||
err.response?.data?.message || err.message || 'Failed to load session';
|
? (err.response?.data?.message || err.message)
|
||||||
|
: (err instanceof Error ? err.message : undefined) || 'Failed to load session';
|
||||||
setError(errorMsg);
|
setError(errorMsg);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -55,9 +57,10 @@ export const useParticipantSession = (token?: string): UseParticipantSessionResu
|
|||||||
if (request.participantToken) {
|
if (request.participantToken) {
|
||||||
await loadSession(request.participantToken);
|
await loadSession(request.participantToken);
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: unknown) {
|
||||||
const errorMsg =
|
const errorMsg = isAxiosError(err)
|
||||||
err.response?.data?.message || err.message || 'Failed to submit signature';
|
? (err.response?.data?.message || err.message)
|
||||||
|
: (err instanceof Error ? err.message : undefined) || 'Failed to submit signature';
|
||||||
setError(errorMsg);
|
setError(errorMsg);
|
||||||
throw new Error(errorMsg, { cause: err });
|
throw new Error(errorMsg, { cause: err });
|
||||||
} finally {
|
} finally {
|
||||||
@@ -79,9 +82,10 @@ export const useParticipantSession = (token?: string): UseParticipantSessionResu
|
|||||||
setParticipant(updatedParticipant);
|
setParticipant(updatedParticipant);
|
||||||
// Reload session
|
// Reload session
|
||||||
await loadSession(token);
|
await loadSession(token);
|
||||||
} catch (err: any) {
|
} catch (err: unknown) {
|
||||||
const errorMsg =
|
const errorMsg = isAxiosError(err)
|
||||||
err.response?.data?.message || err.message || 'Failed to decline';
|
? (err.response?.data?.message || err.message)
|
||||||
|
: (err instanceof Error ? err.message : undefined) || 'Failed to decline';
|
||||||
setError(errorMsg);
|
setError(errorMsg);
|
||||||
throw new Error(errorMsg, { cause: err });
|
throw new Error(errorMsg, { cause: err });
|
||||||
} finally {
|
} finally {
|
||||||
@@ -104,9 +108,10 @@ export const useParticipantSession = (token?: string): UseParticipantSessionResu
|
|||||||
a.click();
|
a.click();
|
||||||
window.URL.revokeObjectURL(url);
|
window.URL.revokeObjectURL(url);
|
||||||
document.body.removeChild(a);
|
document.body.removeChild(a);
|
||||||
} catch (err: any) {
|
} catch (err: unknown) {
|
||||||
const errorMsg =
|
const errorMsg = isAxiosError(err)
|
||||||
err.response?.data?.message || err.message || 'Failed to download document';
|
? (err.response?.data?.message || err.message)
|
||||||
|
: (err instanceof Error ? err.message : undefined) || 'Failed to download document';
|
||||||
setError(errorMsg);
|
setError(errorMsg);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
|
import { isAxiosError } from 'axios';
|
||||||
import { useParams, useNavigate } from 'react-router-dom';
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Stack, Text, Paper, Center, Loader, TextInput, PasswordInput, Anchor } from '@mantine/core';
|
import { Stack, Text, Paper, Center, Loader, TextInput, PasswordInput, Anchor } from '@mantine/core';
|
||||||
@@ -56,14 +57,14 @@ export default function InviteAccept() {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
const response = await apiClient.get<InviteData>(`/api/v1/invite/validate/${token}`, {
|
const response = await apiClient.get<InviteData>(`/api/v1/invite/validate/${token}`, {
|
||||||
suppressErrorToast: true,
|
suppressErrorToast: true,
|
||||||
} as any);
|
});
|
||||||
setInviteData(response.data);
|
setInviteData(response.data);
|
||||||
setError(null);
|
setError(null);
|
||||||
} catch (err: any) {
|
} catch (err: unknown) {
|
||||||
const errorMessage =
|
const errorMessage = isAxiosError(err)
|
||||||
err.response?.data?.error ||
|
? (err.response?.data?.error || err.message)
|
||||||
err.message ||
|
: (err instanceof Error ? err.message : undefined) ||
|
||||||
t('invite.validationError', 'Failed to validate invitation link');
|
t('invite.validationError', 'Failed to validate invitation link');
|
||||||
setError(errorMessage);
|
setError(errorMessage);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -108,15 +109,15 @@ export default function InviteAccept() {
|
|||||||
|
|
||||||
await apiClient.post(`/api/v1/invite/accept/${token}`, formData, {
|
await apiClient.post(`/api/v1/invite/accept/${token}`, formData, {
|
||||||
suppressErrorToast: true,
|
suppressErrorToast: true,
|
||||||
} as any);
|
});
|
||||||
|
|
||||||
// Success - redirect to login
|
// Success - redirect to login
|
||||||
navigate('/login?messageType=accountCreated');
|
navigate('/login?messageType=accountCreated');
|
||||||
} catch (err: any) {
|
} catch (err: unknown) {
|
||||||
const errorMessage =
|
const errorMessage = isAxiosError(err)
|
||||||
err.response?.data?.error ||
|
? (err.response?.data?.error || err.message)
|
||||||
err.message ||
|
: (err instanceof Error ? err.message : undefined) ||
|
||||||
t('invite.acceptError', 'Failed to create account');
|
t('invite.acceptError', 'Failed to create account');
|
||||||
setError(errorMessage);
|
setError(errorMessage);
|
||||||
} finally {
|
} finally {
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useEffect, useMemo, useRef } from 'react';
|
import { useEffect, useMemo, useRef } from 'react';
|
||||||
|
import { isAxiosError } from 'axios';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import apiClient from '@app/services/apiClient';
|
import apiClient from '@app/services/apiClient';
|
||||||
@@ -99,7 +100,7 @@ export default function ShareLinkLoader({ token }: ShareLinkLoaderProps) {
|
|||||||
|
|
||||||
const idMap = new Map<string, FileId>();
|
const idMap = new Map<string, FileId>();
|
||||||
for (let i = 0; i < stirlingFiles.length; i += 1) {
|
for (let i = 0; i < stirlingFiles.length; i += 1) {
|
||||||
idMap.set(sortedEntries[i].logicalId, stirlingFiles[i].fileId as FileId);
|
idMap.set(sortedEntries[i].logicalId, stirlingFiles[i].fileId);
|
||||||
}
|
}
|
||||||
|
|
||||||
const rootIdMap = new Map<string, FileId>();
|
const rootIdMap = new Map<string, FileId>();
|
||||||
@@ -199,9 +200,9 @@ export default function ShareLinkLoader({ token }: ShareLinkLoaderProps) {
|
|||||||
|
|
||||||
navActions.setWorkbench('viewer');
|
navActions.setWorkbench('viewer');
|
||||||
navigate('/', { replace: true });
|
navigate('/', { replace: true });
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
if (signal.aborted) return;
|
if (signal.aborted) return;
|
||||||
const status = error?.response?.status;
|
const status = isAxiosError(error) ? error.response?.status : undefined;
|
||||||
if (status === 401 || status === 403) {
|
if (status === 401 || status === 403) {
|
||||||
if (!isAuthenticated && !authLoading) {
|
if (!isAuthenticated && !authLoading) {
|
||||||
alert({
|
alert({
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
import { isAxiosError } from 'axios';
|
||||||
import { useNavigate, useParams } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
import { Alert, Badge, Button, Group, Loader, Paper, Stack, Text, Title } from '@mantine/core';
|
import { Alert, Badge, Button, Group, Loader, Paper, Stack, Text, Title } from '@mantine/core';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@@ -45,8 +46,8 @@ export default function ShareLinkPage() {
|
|||||||
const data = await fetchShareLinkMetadata(normalizedToken);
|
const data = await fetchShareLinkMetadata(normalizedToken);
|
||||||
setMetadata(data);
|
setMetadata(data);
|
||||||
setStatus('ready');
|
setStatus('ready');
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
const statusCode = error?.response?.status as number | undefined;
|
const statusCode = isAxiosError(error) ? error.response?.status : undefined;
|
||||||
if (statusCode === 401) {
|
if (statusCode === 401) {
|
||||||
setStatus('login');
|
setStatus('login');
|
||||||
} else if (statusCode === 403) {
|
} else if (statusCode === 403) {
|
||||||
@@ -83,8 +84,8 @@ export default function ShareLinkPage() {
|
|||||||
link.click();
|
link.click();
|
||||||
link.remove();
|
link.remove();
|
||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
const statusCode = error?.response?.status as number | undefined;
|
const statusCode = isAxiosError(error) ? error.response?.status : undefined;
|
||||||
if (statusCode === 401) {
|
if (statusCode === 401) {
|
||||||
setStatus('login');
|
setStatus('login');
|
||||||
} else if (statusCode === 403) {
|
} else if (statusCode === 403) {
|
||||||
@@ -118,8 +119,8 @@ export default function ShareLinkPage() {
|
|||||||
}
|
}
|
||||||
navActions.setWorkbench('viewer');
|
navActions.setWorkbench('viewer');
|
||||||
navigate('/', { replace: true });
|
navigate('/', { replace: true });
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
const statusCode = error?.response?.status as number | undefined;
|
const statusCode = isAxiosError(error) ? error.response?.status : undefined;
|
||||||
if (statusCode === 401) {
|
if (statusCode === 401) {
|
||||||
setStatus('login');
|
setStatus('login');
|
||||||
} else if (statusCode === 403) {
|
} else if (statusCode === 403) {
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export interface ShareLinkMetadata {
|
|||||||
export async function fetchShareLinkMetadata(token: string): Promise<ShareLinkMetadata> {
|
export async function fetchShareLinkMetadata(token: string): Promise<ShareLinkMetadata> {
|
||||||
const response = await apiClient.get<ShareLinkMetadata>(
|
const response = await apiClient.get<ShareLinkMetadata>(
|
||||||
`/api/v1/storage/share-links/${token}/metadata`,
|
`/api/v1/storage/share-links/${token}/metadata`,
|
||||||
{ suppressErrorToast: true, skipAuthRedirect: true } as any
|
{ suppressErrorToast: true, skipAuthRedirect: true }
|
||||||
);
|
);
|
||||||
return response.data || {};
|
return response.data || {};
|
||||||
}
|
}
|
||||||
@@ -38,7 +38,7 @@ export async function downloadShareLink(token: string): Promise<{
|
|||||||
responseType: 'blob',
|
responseType: 'blob',
|
||||||
suppressErrorToast: true,
|
suppressErrorToast: true,
|
||||||
skipAuthRedirect: true,
|
skipAuthRedirect: true,
|
||||||
} as any);
|
});
|
||||||
const contentType =
|
const contentType =
|
||||||
(response.headers && (response.headers['content-type'] || response.headers['Content-Type'])) ||
|
(response.headers && (response.headers['content-type'] || response.headers['Content-Type'])) ||
|
||||||
'';
|
'';
|
||||||
@@ -73,7 +73,7 @@ export async function importShareLinkToWorkbench(
|
|||||||
|
|
||||||
const idMap = new Map<string, FileId>();
|
const idMap = new Map<string, FileId>();
|
||||||
for (let i = 0; i < stirlingFiles.length; i += 1) {
|
for (let i = 0; i < stirlingFiles.length; i += 1) {
|
||||||
idMap.set(sortedEntries[i].logicalId, stirlingFiles[i].fileId as FileId);
|
idMap.set(sortedEntries[i].logicalId, stirlingFiles[i].fileId);
|
||||||
}
|
}
|
||||||
|
|
||||||
const rootIdMap = new Map<string, FileId>();
|
const rootIdMap = new Map<string, FileId>();
|
||||||
@@ -141,7 +141,7 @@ export async function importShareLinkToWorkbench(
|
|||||||
autoUnzip: false,
|
autoUnzip: false,
|
||||||
skipAutoUnzip: false,
|
skipAutoUnzip: false,
|
||||||
});
|
});
|
||||||
const ids = stirlingFiles.map((stirlingFile: StirlingFile) => stirlingFile.fileId as FileId);
|
const ids = stirlingFiles.map((stirlingFile: StirlingFile) => stirlingFile.fileId);
|
||||||
if (ids.length > 0) {
|
if (ids.length > 0) {
|
||||||
const sharedUpdates = {
|
const sharedUpdates = {
|
||||||
remoteStorageId: shareMetadata?.fileId,
|
remoteStorageId: shareMetadata?.fileId,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import apiClient from '@app/services/apiClient';
|
import apiClient from '@app/services/apiClient';
|
||||||
|
import type { User } from '@app/services/userManagementService';
|
||||||
|
|
||||||
export interface Team {
|
export interface Team {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -25,6 +26,13 @@ export interface TeamDetailsResponse {
|
|||||||
availableUsers: TeamMember[];
|
availableUsers: TeamMember[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TeamDetailsUIResponse {
|
||||||
|
team: Team;
|
||||||
|
teamUsers: User[];
|
||||||
|
availableUsers: User[];
|
||||||
|
userLastRequest?: Record<string, number>;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Team Management Service
|
* Team Management Service
|
||||||
* Provides functions to interact with team-related backend APIs
|
* Provides functions to interact with team-related backend APIs
|
||||||
@@ -41,8 +49,8 @@ export const teamService = {
|
|||||||
/**
|
/**
|
||||||
* Get team details including members
|
* Get team details including members
|
||||||
*/
|
*/
|
||||||
async getTeamDetails(teamId: number): Promise<any> {
|
async getTeamDetails(teamId: number): Promise<TeamDetailsUIResponse> {
|
||||||
const response = await apiClient.get(`/api/v1/proprietary/ui-data/teams/${teamId}`);
|
const response = await apiClient.get<TeamDetailsUIResponse>(`/api/v1/proprietary/ui-data/teams/${teamId}`);
|
||||||
return response.data;
|
return response.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -54,7 +62,7 @@ export const teamService = {
|
|||||||
formData.append('name', name);
|
formData.append('name', name);
|
||||||
await apiClient.post('/api/v1/team/create', formData, {
|
await apiClient.post('/api/v1/team/create', formData, {
|
||||||
suppressErrorToast: true,
|
suppressErrorToast: true,
|
||||||
} as any);
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -66,7 +74,7 @@ export const teamService = {
|
|||||||
formData.append('newName', newName);
|
formData.append('newName', newName);
|
||||||
await apiClient.post('/api/v1/team/rename', formData, {
|
await apiClient.post('/api/v1/team/rename', formData, {
|
||||||
suppressErrorToast: true,
|
suppressErrorToast: true,
|
||||||
} as any);
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -77,7 +85,7 @@ export const teamService = {
|
|||||||
formData.append('teamId', teamId.toString());
|
formData.append('teamId', teamId.toString());
|
||||||
await apiClient.post('/api/v1/team/delete', formData, {
|
await apiClient.post('/api/v1/team/delete', formData, {
|
||||||
suppressErrorToast: true,
|
suppressErrorToast: true,
|
||||||
} as any);
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -89,7 +97,7 @@ export const teamService = {
|
|||||||
formData.append('userId', userId.toString());
|
formData.append('userId', userId.toString());
|
||||||
await apiClient.post('/api/v1/team/addUser', formData, {
|
await apiClient.post('/api/v1/team/addUser', formData, {
|
||||||
suppressErrorToast: true,
|
suppressErrorToast: true,
|
||||||
} as any);
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -102,6 +110,6 @@ export const teamService = {
|
|||||||
formData.append('teamId', teamId.toString());
|
formData.append('teamId', teamId.toString());
|
||||||
await apiClient.post('/api/v1/user/admin/changeRole', formData, {
|
await apiClient.post('/api/v1/user/admin/changeRole', formData, {
|
||||||
suppressErrorToast: true,
|
suppressErrorToast: true,
|
||||||
} as any);
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export interface AdminSettingsData {
|
|||||||
disabledUsers: number;
|
disabledUsers: number;
|
||||||
currentUsername?: string;
|
currentUsername?: string;
|
||||||
roleDetails?: Record<string, string>;
|
roleDetails?: Record<string, string>;
|
||||||
teams?: any[];
|
teams?: unknown[];
|
||||||
maxPaidUsers?: number;
|
maxPaidUsers?: number;
|
||||||
// License information
|
// License information
|
||||||
maxAllowedUsers: number;
|
maxAllowedUsers: number;
|
||||||
@@ -39,7 +39,7 @@ export interface AdminSettingsData {
|
|||||||
licenseMaxUsers: number;
|
licenseMaxUsers: number;
|
||||||
premiumEnabled: boolean;
|
premiumEnabled: boolean;
|
||||||
mailEnabled: boolean;
|
mailEnabled: boolean;
|
||||||
userSettings?: Record<string, any>;
|
userSettings?: Record<string, unknown>;
|
||||||
lockedUsers?: string[];
|
lockedUsers?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,7 +155,7 @@ export const userManagementService = {
|
|||||||
}
|
}
|
||||||
await apiClient.post('/api/v1/user/admin/saveUser', formData, {
|
await apiClient.post('/api/v1/user/admin/saveUser', formData, {
|
||||||
suppressErrorToast: true, // Component will handle error display
|
suppressErrorToast: true, // Component will handle error display
|
||||||
} as any);
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -170,7 +170,7 @@ export const userManagementService = {
|
|||||||
}
|
}
|
||||||
await apiClient.post('/api/v1/user/admin/changeRole', formData, {
|
await apiClient.post('/api/v1/user/admin/changeRole', formData, {
|
||||||
suppressErrorToast: true,
|
suppressErrorToast: true,
|
||||||
} as any);
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -181,7 +181,7 @@ export const userManagementService = {
|
|||||||
formData.append('enabled', enabled.toString());
|
formData.append('enabled', enabled.toString());
|
||||||
await apiClient.post(`/api/v1/user/admin/changeUserEnabled/${username}`, formData, {
|
await apiClient.post(`/api/v1/user/admin/changeUserEnabled/${username}`, formData, {
|
||||||
suppressErrorToast: true,
|
suppressErrorToast: true,
|
||||||
} as any);
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -190,7 +190,7 @@ export const userManagementService = {
|
|||||||
async deleteUser(username: string): Promise<void> {
|
async deleteUser(username: string): Promise<void> {
|
||||||
await apiClient.post(`/api/v1/user/admin/deleteUser/${username}`, null, {
|
await apiClient.post(`/api/v1/user/admin/deleteUser/${username}`, null, {
|
||||||
suppressErrorToast: true,
|
suppressErrorToast: true,
|
||||||
} as any);
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -211,7 +211,7 @@ export const userManagementService = {
|
|||||||
formData,
|
formData,
|
||||||
{
|
{
|
||||||
suppressErrorToast: true, // Component will handle error display
|
suppressErrorToast: true, // Component will handle error display
|
||||||
} as any
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
return response.data;
|
return response.data;
|
||||||
@@ -245,7 +245,7 @@ export const userManagementService = {
|
|||||||
formData,
|
formData,
|
||||||
{
|
{
|
||||||
suppressErrorToast: true,
|
suppressErrorToast: true,
|
||||||
} as any
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
return response.data;
|
return response.data;
|
||||||
@@ -265,7 +265,7 @@ export const userManagementService = {
|
|||||||
async revokeInviteLink(inviteId: number): Promise<void> {
|
async revokeInviteLink(inviteId: number): Promise<void> {
|
||||||
await apiClient.delete(`/api/v1/invite/revoke/${inviteId}`, {
|
await apiClient.delete(`/api/v1/invite/revoke/${inviteId}`, {
|
||||||
suppressErrorToast: true,
|
suppressErrorToast: true,
|
||||||
} as any);
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -300,7 +300,7 @@ export const userManagementService = {
|
|||||||
|
|
||||||
await apiClient.post('/api/v1/user/admin/changePasswordForUser', formData, {
|
await apiClient.post('/api/v1/user/admin/changePasswordForUser', formData, {
|
||||||
suppressErrorToast: true, // Component will handle error display
|
suppressErrorToast: true, // Component will handle error display
|
||||||
} as any);
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -309,7 +309,7 @@ export const userManagementService = {
|
|||||||
async unlockUser(username: string): Promise<void> {
|
async unlockUser(username: string): Promise<void> {
|
||||||
await apiClient.post(`/api/v1/user/admin/unlockUser/${username}`, null, {
|
await apiClient.post(`/api/v1/user/admin/unlockUser/${username}`, null, {
|
||||||
suppressErrorToast: true,
|
suppressErrorToast: true,
|
||||||
} as any);
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user