Merge pull request 'fix/admin-panel-bugs-4' (#75) from fix/admin-panel-bugs-4 into release/v9.1.0

Reviewed-on: https://git.onlyoffice.com/ONLYOFFICE/server/pulls/75
This commit is contained in:
Oleg Korshul
2025-10-09 21:33:22 +00:00
8 changed files with 44 additions and 36 deletions

View File

@ -1,6 +1,5 @@
import {useEffect, useState} from 'react';
import {useDispatch, useSelector} from 'react-redux';
import {useLocation, useNavigate} from 'react-router-dom';
import {fetchUser, selectUser, selectUserLoading, selectIsAuthenticated} from '../../store/slices/userSlice';
import {checkSetupRequired} from '../../api';
import Spinner from '../../assets/Spinner.svg';
@ -10,8 +9,6 @@ import ServerUnavailable from '../ServerUnavailable/ServerUnavailable';
export default function AuthWrapper({children}) {
const dispatch = useDispatch();
const location = useLocation();
const navigate = useNavigate();
const user = useSelector(selectUser);
const loading = useSelector(selectUserLoading);
const isAuthenticated = useSelector(selectIsAuthenticated);
@ -20,24 +17,6 @@ export default function AuthWrapper({children}) {
const [checkingSetup, setCheckingSetup] = useState(true);
const [serverUnavailable, setServerUnavailable] = useState(false);
// Save intended URL for redirect after setup/login
useEffect(() => {
if (!isAuthenticated && location.pathname !== '/admin/setup' && location.pathname !== '/admin/login') {
sessionStorage.setItem('redirectAfterAuth', location.pathname + location.search);
}
}, [location, isAuthenticated]);
// Redirect after successful authentication
useEffect(() => {
if (isAuthenticated && user) {
const redirectUrl = sessionStorage.getItem('redirectAfterAuth');
if (redirectUrl) {
sessionStorage.removeItem('redirectAfterAuth');
navigate(redirectUrl, {replace: true});
}
}
}, [isAuthenticated, user, navigate]);
useEffect(() => {
const checkSetup = async () => {
try {

View File

@ -194,6 +194,8 @@
}
AI.onLoadInternalProviders();
if (Asc.plugin.sendEvent)
Asc.plugin.sendEvent("ai_onLoadInternalProviders");
};
AI.InternalCustomProvidersSources = {};

View File

@ -1,7 +1,13 @@
import {useState, useEffect, useCallback} from 'react';
import {useSelector, useDispatch} from 'react-redux';
import {selectConfig, saveConfig} from '../../../store/slices/configSlice';
import {registerShowWindowCallback, registerCloseWindowCallback, registerSaveCallback, initAISettings} from '../js/plugins-sdk';
import {
registerShowWindowCallback,
registerCloseWindowCallback,
registerSaveCallback,
registerLoadInternalProvidersCallback,
initAISettings
} from '../js/plugins-sdk';
/**
* Custom hook for managing complete AI plugin functionality
@ -11,6 +17,7 @@ import {registerShowWindowCallback, registerCloseWindowCallback, registerSaveCal
*/
const useAiPlugin = statisticsData => {
const [pluginWindows, setPluginWindows] = useState([]);
const [internalProvidersLoaded, setInternalProvidersLoaded] = useState(false);
const dispatch = useDispatch();
const config = useSelector(selectConfig);
@ -121,16 +128,22 @@ const useAiPlugin = statisticsData => {
}
};
const handleLoadInternalProviders = () => {
setInternalProvidersLoaded(true);
};
// Register all callbacks with SDK
registerShowWindowCallback(handleShowWindow);
registerCloseWindowCallback(handleCloseWindow);
registerSaveCallback(handleSave);
registerLoadInternalProvidersCallback(handleLoadInternalProviders);
// Cleanup: unregister all callbacks
return () => {
registerShowWindowCallback(null);
registerCloseWindowCallback(null);
registerSaveCallback(null);
registerLoadInternalProvidersCallback(null);
};
}, [config, dispatch]);
@ -139,7 +152,8 @@ const useAiPlugin = statisticsData => {
return {
pluginWindows,
currentWindow,
handleIframeLoad
handleIframeLoad,
internalProvidersLoaded
};
};

View File

@ -23,7 +23,7 @@ export default function AiIntegration() {
});
// Use custom hook for complete AI plugin functionality
const {currentWindow, handleIframeLoad} = useAiPlugin(data);
const {currentWindow, handleIframeLoad, internalProvidersLoaded} = useAiPlugin(data);
// Constants
const AI_IFRAME_SRC = `ai/index.html`;
@ -58,7 +58,8 @@ export default function AiIntegration() {
return (
<div style={{display: 'flex', flexDirection: 'column', height: '100%', position: 'relative'}}>
<iframe id={AI_IFRAME_ID} title='AI Settings' src={AI_IFRAME_SRC} style={iframeStyle} onLoad={() => handleIframeLoad(AI_IFRAME_ID)} />
{currentWindow && (
{!internalProvidersLoaded && <div>Please, wait...</div>}
{internalProvidersLoaded && currentWindow && (
<div key={currentWindow.iframeId} style={{width: '100%', height: '100%'}}>
<PageHeader>{currentWindow.description || ''} </PageHeader>
<iframe id={currentWindow.iframeId} title={currentWindow.description || ''} src={currentWindow.url} style={pluginWindowStyle} />

View File

@ -9,6 +9,7 @@ const mainButtonId = 'settings.html';
let showPluginWindowCallback = null;
let closePluginWindowCallback = null;
let saveCallback = null;
let loadInternalProvidersCallback = null;
let settingsButton = null;
@ -390,6 +391,11 @@ function handleMethod(data) {
} else if (data.methodName === 'CloseWindow') {
CloseWindow(data.data);
handleMethodReturn(undefined);
} else if (data.methodName === 'SendEvent') {
if (data.data && data.data[0] === 'ai_onLoadInternalProviders' && loadInternalProvidersCallback) {
loadInternalProvidersCallback();
}
handleMethodReturn(undefined);
} else {
handleMethodReturn(undefined);
}
@ -543,4 +549,12 @@ function registerSaveCallback(callback) {
saveCallback = callback;
}
export {initAISettings, registerShowWindowCallback, registerCloseWindowCallback, registerSaveCallback};
/**
* Registers callback for loading internal providers
* @param {function} callback - Function to call when internal providers should be loaded () => void
*/
function registerLoadInternalProvidersCallback(callback) {
loadInternalProvidersCallback = callback;
}
export {initAISettings, registerShowWindowCallback, registerCloseWindowCallback, registerSaveCallback, registerLoadInternalProvidersCallback};

View File

@ -67,9 +67,9 @@ router.patch('/', validateJWT, rawFileParser, async (req, res) => {
await runtimeConfigManager.saveConfig(ctx, validationResult.value);
}
await ctx.initTenantCache();
const filteredConfig = getScopedConfig(ctx);
res.status(200).json(filteredConfig);
const newConfig = await runtimeConfigManager.getConfig(ctx);
res.status(200).json(newConfig);
} catch (error) {
ctx.logger.error('Configuration save error: %s', error.stack);
res.status(500).json({error: 'Internal server error', details: error.message});

View File

@ -38,7 +38,7 @@ const utils = require('../../../../../Common/sources/utils');
const runtimeConfigManager = require('../../../../../Common/sources/runtimeConfigManager');
const tenantManager = require('../../../../../Common/sources/tenantManager');
const {validateJWT} = require('../../middleware/auth');
const {getScopedConfig} = require('../config/config.service');
const {getConfig} = require('../../../../../Common/sources/runtimeConfigManager');
const cookieParser = require('cookie-parser');
const router = express.Router();
@ -156,8 +156,8 @@ router.post('/rotate-keys', validateJWT, express.json(), async (req, res) => {
try {
ctx.logger.info('WOPI key rotation start');
const currentConfig = ctx.getFullCfg();
const wopiConfig = utils.getImpl(currentConfig, 'wopi') || {};
const currentConfig = await getConfig(ctx);
const wopiConfig = currentConfig.wopi || {};
const newWopiConfig = generateWopiKeys();
@ -184,9 +184,7 @@ router.post('/rotate-keys', validateJWT, express.json(), async (req, res) => {
await runtimeConfigManager.saveConfig(ctx, newConfig);
}
await ctx.initTenantCache();
const filteredConfig = getScopedConfig(ctx);
res.status(200).json(filteredConfig);
res.status(200).json(newConfig);
} catch (error) {
ctx.logger.error('WOPI key rotation error: %s', error.stack);
res.status(500).json({

View File

@ -201,7 +201,7 @@ function concatParams(...parameters) {
function getTableColumns(ctx, tableName) {
const values = [];
const sqlParam = addSqlParameter(tableName, values);
const sqlCommand = `SELECT column_name FROM information_schema.COLUMNS WHERE TABLE_NAME = ${sqlParam} AND TABLE_SCHEMA = 'dbo';`;
const sqlCommand = `SELECT column_name FROM information_schema.COLUMNS WHERE TABLE_NAME = ${sqlParam} AND TABLE_SCHEMA = SCHEMA_NAME();`;
return executeQuery(ctx, sqlCommand, values);
}