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

Reviewed-on: https://git.onlyoffice.com/ONLYOFFICE/server/pulls/65
This commit is contained in:
Sergey Konovalov
2025-09-29 23:28:46 +00:00
12 changed files with 50 additions and 40 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 144 KiB

View File

@ -1,18 +1,17 @@
import {useSelector, useDispatch} from 'react-redux';
import {useDispatch} from 'react-redux';
import {useLocation, useNavigate} from 'react-router-dom';
import {selectIsAuthenticated} from '../../store/slices/userSlice';
import {clearConfig} from '../../store/slices/configSlice';
import {logout} from '../../api';
import MenuItem from './MenuItem/MenuItem';
import AppMenuLogo from '../../assets/AppMenuLogo.svg';
import {menuItems} from '../../config/menuItems';
import styles from './Menu.module.scss';
import FileIcon from '../../assets/File.svg';
function Menu() {
const location = useLocation();
const navigate = useNavigate();
const dispatch = useDispatch();
const isAuthenticated = useSelector(selectIsAuthenticated);
const handleLogout = async () => {
try {
@ -49,17 +48,16 @@ function Menu() {
<div className={styles['menu__menuItems']}>
{menuItems.map(item => (
<MenuItem key={item.key} label={item.label} isActive={isActiveItem(item.path)} onClick={() => handleMenuItemClick(item)} />
<MenuItem
key={item.key}
label={item.label}
isActive={isActiveItem(item.path)}
onClick={() => handleMenuItemClick(item)}
icon={FileIcon}
/>
))}
<MenuItem label='Logout' isActive={false} onClick={handleLogout} />
</div>
{isAuthenticated && (
<div className={styles['menu__logoutContainer']}>
<button onClick={handleLogout} className={styles['menu__logoutButton']}>
Logout
</button>
</div>
)}
</div>
</div>
);

View File

@ -60,7 +60,7 @@
}
&__logoutContainer {
margin-top: auto;
margin-left: 48px;
}
&__logoutButton {

View File

@ -1,10 +1,9 @@
import FileIcon from '../../../assets/File.svg';
import styles from './MenuItem.module.scss';
function MenuItem({label, isActive, onClick}) {
function MenuItem({label, isActive, onClick, icon}) {
return (
<div className={`${styles.menuItem} ${isActive ? styles['menuItem--active'] : ''}`} onClick={onClick}>
<img src={FileIcon} alt='' className={styles['menuItem__icon']} />
{icon ? <img src={icon} alt='' className={styles['menuItem__icon']} /> : <div className={styles['menuItem__icon']} />}
<span className={styles['menuItem__label']}>{label}</span>
</div>
);

View File

@ -20,7 +20,7 @@
}
&--disabled {
background: #ff865c;
background: #ffd4c5;
cursor: not-allowed;
}

View File

@ -137,34 +137,34 @@ function Expiration() {
<div className={styles.tabPanel}>
<div className={styles.formRow}>
<Input
label='Files Cron Expression'
label='Cache Cleanup Cron Expression'
value={localSettings.filesCron}
onChange={value => handleFieldChange('filesCron', value)}
placeholder='0 0 */2 * * *'
description='Cron expression for file cleanup schedule (6 fields: second minute hour day month day_of_week)'
description='Cron expression for cleaning up expired cached files and temporary data (6 fields: second minute hour day month day_of_week)'
error={getFieldError(CONFIG_PATHS.filesCron)}
/>
</div>
<div className={styles.formRow}>
<Input
label='Documents Cron Expression'
label='Auto-Save & Presence Cleanup Cron Expression'
value={localSettings.documentsCron}
onChange={value => handleFieldChange('documentsCron', value)}
placeholder='0 0 */2 * * *'
description='Cron expression for document cleanup schedule (6 fields: second minute hour day month day_of_week)'
description='Cron expression for auto-saving documents with pending changes and cleaning up expired user presence data (6 fields: second minute hour day month day_of_week)'
error={getFieldError(CONFIG_PATHS.documentsCron)}
/>
</div>
<div className={styles.formRow}>
<Input
label='Files Expiration Time (seconds)'
label='Cache File Retention Time (seconds)'
type='number'
value={localSettings.files}
onChange={value => handleFieldChange('files', value)}
placeholder='3600'
description='Time in seconds after which files expire and can be cleaned up'
description='How long to keep cached files before marking them as expired and eligible for cleanup (default: 86400 = 24 hours)'
min='0'
error={getFieldError(CONFIG_PATHS.files)}
/>

View File

@ -46,6 +46,7 @@ export default function Login() {
onChange={setTenantName}
placeholder='Enter your tenant name'
description='The name of your tenant organization'
error={error}
onKeyDown={handleKeyDown}
/>
</div>

View File

@ -107,3 +107,6 @@ app.use((err, req, res, _next) => {
server.listen(port, () => {
operationContext.global.logger.warn('AdminPanel server listening on port %d', port);
});
//after all required modules in all files
moduleReloader.finalizeConfigWithRuntime();

View File

@ -50,6 +50,10 @@ function reloadNpmModule(moduleName) {
}
}
// Backup original NODE_CONFIG to avoid growing environment
const prevNodeConfig = process.env.NODE_CONFIG;
let nodeConfigOverridden = false;
/**
* Requires config module with runtime configuration support.
* Temporarily sets NODE_CONFIG for reload, then restores environment to prevent E2BIG.
@ -59,10 +63,6 @@ function reloadNpmModule(moduleName) {
function requireConfigWithRuntime(opt_additionalConfig) {
let config = require('config');
// Backup original NODE_CONFIG to avoid growing environment
const prevNodeConfig = process.env.NODE_CONFIG;
let nodeConfigOverridden = false;
try {
const configFilePath = config.get('runtimeConfig.filePath');
if (configFilePath) {
@ -88,20 +88,23 @@ function requireConfigWithRuntime(opt_additionalConfig) {
if (err.code !== 'ENOENT') {
console.error('Failed to load runtime config: %s', err.stack);
}
} finally {
// Restore original NODE_CONFIG to keep env small and avoid E2BIG on Windows/pkg
if (nodeConfigOverridden) {
if (typeof prevNodeConfig === 'undefined') {
delete process.env.NODE_CONFIG;
} else {
process.env.NODE_CONFIG = prevNodeConfig;
}
}
}
return config;
}
function finalizeConfigWithRuntime() {
// Restore original NODE_CONFIG to keep env small and avoid E2BIG on Windows/pkg
if (nodeConfigOverridden) {
if (typeof prevNodeConfig === 'undefined') {
delete process.env.NODE_CONFIG;
} else {
process.env.NODE_CONFIG = prevNodeConfig;
}
}
}
module.exports = {
reloadNpmModule,
requireConfigWithRuntime
requireConfigWithRuntime,
finalizeConfigWithRuntime
};

View File

@ -509,3 +509,6 @@ process.on('uncaughtException', err => {
process.exit(1);
});
});
//after all required modules in all files
moduleReloader.finalizeConfigWithRuntime();

View File

@ -66,7 +66,7 @@ function initializeSharp() {
}
});
}
if (sharp) {
// todo test.
// Set concurrency to 2 for better performance

View File

@ -33,10 +33,10 @@
'use strict';
const cluster = require('cluster');
const logger = require('./../../Common/sources/logger');
const operationContext = require('./../../Common/sources/operationContext');
const moduleReloader = require('./../../Common/sources/moduleReloader');
const config = moduleReloader.requireConfigWithRuntime();
const logger = require('./../../Common/sources/logger');
const operationContext = require('./../../Common/sources/operationContext');
if (cluster.isMaster) {
const fs = require('fs');
@ -104,3 +104,6 @@ process.on('uncaughtException', err => {
process.exit(1);
});
});
//after all required modules in all files
moduleReloader.finalizeConfigWithRuntime();