mirror of
https://github.com/ONLYOFFICE/server.git
synced 2026-04-07 14:04:35 +08:00
[config] Add runtimeConfig config property; Fix bug in aiProxyHandler.js
This commit is contained in:
@ -30,6 +30,14 @@
|
||||
"replaceConsole": true
|
||||
}
|
||||
},
|
||||
"runtimeConfig": {
|
||||
"filePath": "",
|
||||
"cache": {
|
||||
"stdTTL": 300,
|
||||
"checkperiod": 60,
|
||||
"useClones": false
|
||||
}
|
||||
},
|
||||
"queue": {
|
||||
"type": "rabbitmq",
|
||||
"visibilityTimeout": 300,
|
||||
|
||||
@ -2,6 +2,9 @@
|
||||
"log": {
|
||||
"filePath": "../Common/config/log4js/development.json"
|
||||
},
|
||||
"runtimeConfig": {
|
||||
"filePath": "./../runtime.json"
|
||||
},
|
||||
"queue": {
|
||||
"visibilityTimeout": 900
|
||||
},
|
||||
|
||||
@ -2,6 +2,9 @@
|
||||
"log": {
|
||||
"filePath": "../Common/config/log4js/development.json"
|
||||
},
|
||||
"runtimeConfig": {
|
||||
"filePath": "./../runtime.json"
|
||||
},
|
||||
"queue": {
|
||||
"visibilityTimeout": 900
|
||||
},
|
||||
|
||||
@ -2,6 +2,9 @@
|
||||
"log": {
|
||||
"filePath": "../Common/config/log4js/development.json"
|
||||
},
|
||||
"runtimeConfig": {
|
||||
"filePath": "./../runtime.json"
|
||||
},
|
||||
"queue": {
|
||||
"visibilityTimeout": 900
|
||||
},
|
||||
|
||||
@ -5,6 +5,9 @@
|
||||
"aiSettings": {
|
||||
"pluginDir" : "/var/www/onlyoffice/documentserver/server/info/ai"
|
||||
},
|
||||
"runtimeConfig": {
|
||||
"filePath": "/var/www/onlyoffice/documentserver/../Data/runtime.json"
|
||||
},
|
||||
"storage": {
|
||||
"fs": {
|
||||
"folderPath": "/var/lib/onlyoffice/documentserver/App_Data/cache/files"
|
||||
|
||||
@ -2,6 +2,9 @@
|
||||
"log": {
|
||||
"filePath": "../../config/log4js/production.json"
|
||||
},
|
||||
"runtimeConfig": {
|
||||
"filePath": "./../runtime.json"
|
||||
},
|
||||
"aiSettings": {
|
||||
"pluginDir" : "../info/ai"
|
||||
},
|
||||
|
||||
@ -32,10 +32,12 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
const config = require('config');
|
||||
const utils = require('./utils');
|
||||
const logger = require('./logger');
|
||||
const constants = require('./constants');
|
||||
const tenantManager = require('./tenantManager');
|
||||
const runtimeConfigManager = require('./runtimeConfigManager');
|
||||
|
||||
function Context(){
|
||||
this.logger = logger.getLogger('nodeJS');
|
||||
@ -85,7 +87,10 @@ Context.prototype.initFromPubSub = function(data) {
|
||||
this.init(ctx.tenant, ctx.docId, ctx.userId, ctx.shardKey, ctx.wopiSrc);
|
||||
};
|
||||
Context.prototype.initTenantCache = async function() {
|
||||
this.config = await tenantManager.getTenantConfig(this);
|
||||
const runtimeConfig = await runtimeConfigManager.getConfig(this);
|
||||
const tenantConfig = await tenantManager.getTenantConfig(this);
|
||||
this.config = utils.deepMergeObjects({}, runtimeConfig, tenantConfig);
|
||||
|
||||
//todo license and secret
|
||||
};
|
||||
|
||||
@ -122,6 +127,13 @@ Context.prototype.getCfg = function(property, defaultValue) {
|
||||
}
|
||||
return defaultValue;
|
||||
};
|
||||
/**
|
||||
* Get the full configuration by combining system config with context config
|
||||
* @returns {object} The merged configuration object
|
||||
*/
|
||||
Context.prototype.getFullCfg = function() {
|
||||
return {...config.util.toObject(), ...this.config};
|
||||
};
|
||||
|
||||
/**
|
||||
* Underlying get mechanism
|
||||
|
||||
@ -39,7 +39,7 @@ const license = require('./../../Common/sources/license');
|
||||
const constants = require('./../../Common/sources/constants');
|
||||
const commonDefines = require('./../../Common/sources/commondefines');
|
||||
const utils = require('./../../Common/sources/utils');
|
||||
const { readFile, readdir } = require('fs/promises');
|
||||
const { readFile, readdir, writeFile } = require('fs/promises');
|
||||
const path = require('path');
|
||||
|
||||
const cfgTenantsBaseDomain = config.get('tenants.baseDomain');
|
||||
@ -123,6 +123,22 @@ async function getTenantConfig(ctx) {
|
||||
}
|
||||
return res;
|
||||
}
|
||||
/**
|
||||
* Set tenant configuration for the current context
|
||||
* @param {operationContext} ctx - Operation context
|
||||
* @param {Object} config - Configuration data to save
|
||||
* @returns {Object} Saved configuration object
|
||||
*/
|
||||
async function setTenantConfig(ctx, config) {
|
||||
let newConfig = await getTenantConfig(ctx);
|
||||
if (isMultitenantMode(ctx) && !isDefaultTenant(ctx)) {
|
||||
newConfig = {...newConfig, ...config};
|
||||
await writeFile(configPath, JSON.stringify(newConfig, null, 2), 'utf8');
|
||||
nodeCache.set(configPath, newConfig);
|
||||
}
|
||||
return newConfig;
|
||||
}
|
||||
|
||||
function getTenantSecret(ctx, type) {
|
||||
return co(function*() {
|
||||
let cfgTenant;
|
||||
@ -428,6 +444,7 @@ exports.getTenantSecret = getTenantSecret;
|
||||
exports.getTenantLicense = getTenantLicense;
|
||||
exports.getServerLicense = getServerLicense;
|
||||
exports.setDefLicense = setDefLicense;
|
||||
exports.setTenantConfig = setTenantConfig;
|
||||
exports.isMultitenantMode = isMultitenantMode;
|
||||
exports.setMultitenantMode = setMultitenantMode;
|
||||
exports.isDefaultTenant = isDefaultTenant;
|
||||
|
||||
@ -47,6 +47,7 @@ const cfgAiApiAllowedOrigins = config.get('aiSettings.allowedCorsOrigins');
|
||||
const cfgAiApiTimeout = config.get('aiSettings.timeout');
|
||||
const cfgAiApiCache = config.get('aiSettings.cache');
|
||||
const cfgTokenEnableBrowser = config.get('services.CoAuthoring.token.enable.browser');
|
||||
const cfgAiSettings = config.get('aiSettings');
|
||||
|
||||
const AI = aiEngine.AI;
|
||||
const nodeCache = new utils.NodeCache(cfgAiApiCache);
|
||||
@ -113,6 +114,7 @@ async function proxyRequest(req, res) {
|
||||
try {
|
||||
ctx.logger.info('Start proxyRequest');
|
||||
const tenTokenEnableBrowser = ctx.getCfg('services.CoAuthoring.token.enable.browser', cfgTokenEnableBrowser);
|
||||
const tenAiApi = ctx.getCfg('aiSettings', cfgAiSettings);
|
||||
|
||||
// 1. Handle CORS preflight (OPTIONS) requests if necessary
|
||||
if (handleCorsHeaders(req, res, ctx) === true) {
|
||||
@ -146,6 +148,10 @@ async function proxyRequest(req, res) {
|
||||
// Find the provider that matches the target URL
|
||||
for (let providerName in AI.Providers) {//todo try for of
|
||||
if (body.target.includes(AI.Providers[providerName].url)) {
|
||||
if (tenAiApi?.providers?.[providerName]) {
|
||||
AI.Providers[providerName].key = tenAiApi.providers[providerName].key;
|
||||
AI.Providers[providerName].url = tenAiApi.providers[providerName].url;
|
||||
}
|
||||
providerHeaders = AI._getHeaders(AI.Providers[providerName]);
|
||||
break;
|
||||
}
|
||||
@ -279,7 +285,8 @@ async function getPluginSettings(ctx) {
|
||||
};
|
||||
try {
|
||||
// Get AI API configuration
|
||||
const aiApi = config.get('aiSettings');
|
||||
const tenAiApi = ctx.getCfg('aiSettings', cfgAiSettings);
|
||||
return tenAiApi;
|
||||
// Process providers and their models if configuration exists
|
||||
if (aiApi?.providers && typeof aiApi.providers === 'object') {
|
||||
const providers = AI.serializeProviders();
|
||||
@ -347,11 +354,13 @@ async function requestModels(req, res) {
|
||||
try {
|
||||
await ctx.initTenantCache();
|
||||
let body = JSON.parse(req.body);
|
||||
if (body.key && AI.Providers[body.name]) {
|
||||
if (AI.Providers[body.name]) {
|
||||
AI.Providers[body.name].key = body.key;
|
||||
AI.Providers[body.name].url = body.url;
|
||||
}
|
||||
let models = await AI.getModels(body);
|
||||
res.json(models);
|
||||
let getRes = await AI.getModels(body);
|
||||
getRes.modelsApi = AI.TmpProviderForModels?.models;
|
||||
res.json(getRes);
|
||||
} catch (error) {
|
||||
ctx.logger.error('getModels error: %s', error.stack);
|
||||
res.sendStatus(400);
|
||||
|
||||
@ -31,13 +31,11 @@
|
||||
*/
|
||||
|
||||
const config = require('config');
|
||||
const { readFile, writeFile, stat, cp } = require('fs/promises');
|
||||
const path = require('path');
|
||||
const express = require('express');
|
||||
const bodyParser = require('body-parser');
|
||||
const tenantManager = require('../../../Common/sources/tenantManager');
|
||||
const operationContext = require('../../../Common/sources/operationContext');
|
||||
const aiProxyHandler = require('../ai/aiProxyHandler');
|
||||
const runtimeConfigManager = require('../../../Common/sources/runtimeConfigManager');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
@ -51,16 +49,8 @@ router.get('/', async (req, res) => {
|
||||
ctx.initFromRequest(req);
|
||||
await ctx.initTenantCache();
|
||||
ctx.logger.debug('config get start');
|
||||
if (tenantManager.isMultitenantMode(ctx) && !tenantManager.isDefaultTenant(ctx)) {
|
||||
//todo
|
||||
}
|
||||
|
||||
let configPath = path.join(process.env.NODE_CONFIG_DIR, 'local.json');
|
||||
try {
|
||||
result = await readFile(configPath, {encoding: 'utf8'});
|
||||
} catch (e) {
|
||||
ctx.logger.debug('config get error: %s', e.stack);
|
||||
}
|
||||
let cfg = ctx.getFullCfg();
|
||||
result = JSON.stringify(cfg);
|
||||
} catch (error) {
|
||||
ctx.logger.error('config get error: %s', error.stack);
|
||||
}
|
||||
@ -76,32 +66,15 @@ router.post('/', rawFileParser, async (req, res) => {
|
||||
try {
|
||||
ctx.initFromRequest(req);
|
||||
await ctx.initTenantCache();
|
||||
if (tenantManager.isMultitenantMode(ctx) && !tenantManager.isDefaultTenant(ctx)) {
|
||||
//todo
|
||||
}
|
||||
|
||||
let newConfig = JSON.parse(req.body);
|
||||
// Define file paths
|
||||
let configPath = path.join(process.env.NODE_CONFIG_DIR, 'local.json');
|
||||
let backupPath = path.join(process.env.NODE_CONFIG_DIR, 'local.json.bak');
|
||||
|
||||
// Create backup of current config before saving
|
||||
let sampleFileStat = null;
|
||||
try {
|
||||
sampleFileStat = await stat(backupPath);
|
||||
} catch (backupError) {
|
||||
ctx.logger.debug('Configuration backup not found: %s', backupError.stack);
|
||||
|
||||
if (tenantManager.isMultitenantMode(ctx) && !tenantManager.isDefaultTenant(ctx)) {
|
||||
await tenantManager.setTenantConfig(ctx, newConfig);
|
||||
} else {
|
||||
await runtimeConfigManager.saveConfig(ctx, newConfig);
|
||||
}
|
||||
if(!sampleFileStat){
|
||||
await cp(configPath, backupPath, {force: true, recursive: true});
|
||||
}
|
||||
try {
|
||||
const oldConfig = JSON.parse(await readFile(configPath, {encoding: 'utf8'}));
|
||||
newConfig = {...oldConfig, ...newConfig};
|
||||
} catch (error) {
|
||||
ctx.logger.debug('Configuration local.json not found: %s', error.stack);
|
||||
}
|
||||
const prettyConfig = JSON.stringify(newConfig, null, 2);
|
||||
await writeFile(configPath, prettyConfig, {encoding: 'utf8'});
|
||||
|
||||
res.sendStatus(200);
|
||||
} catch (error) {
|
||||
ctx.logger.error('Configuration save error: %s', error.stack);
|
||||
|
||||
@ -22,7 +22,7 @@ body {
|
||||
|
||||
#actions-list {
|
||||
position: relative;
|
||||
height: 230px;
|
||||
height: 350px;
|
||||
margin-top: 32px;
|
||||
}
|
||||
|
||||
|
||||
@ -229,6 +229,10 @@ const AIIntegration = {
|
||||
|
||||
goBack() {
|
||||
this.navigateToView('settings');
|
||||
|
||||
if (this.onBack) {
|
||||
this.onBack();
|
||||
}
|
||||
},
|
||||
|
||||
ok() {
|
||||
|
||||
@ -36,6 +36,7 @@
|
||||
|
||||
var settings = null;
|
||||
var framesToInit = [];
|
||||
var tempModels = null;
|
||||
var urlSettings = 'plugin/settings';
|
||||
var urlModels = 'plugin/models';
|
||||
var urlConfig = 'config';
|
||||
@ -53,15 +54,7 @@
|
||||
}
|
||||
});
|
||||
AIIntegration.onSave = function() {
|
||||
var settingsFiltered = Object.assign({}, settings);
|
||||
if (settingsFiltered.providers) {
|
||||
for (var id in settingsFiltered.providers) {
|
||||
if (settingsFiltered.providers.hasOwnProperty(id)) {
|
||||
settingsFiltered.providers[id].models = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
var config = {aiSettings: settingsFiltered};
|
||||
var config = {aiSettings: settings};
|
||||
return putConfig(config).then(function() {
|
||||
return true;
|
||||
}).catch(function() {
|
||||
@ -77,6 +70,13 @@
|
||||
}
|
||||
return;
|
||||
};
|
||||
AIIntegration.onBack = function() {
|
||||
var settingsWindow = findIframeBySrcPart('settings');
|
||||
if(settingsWindow) {
|
||||
updateActions(settingsWindow.contentWindow);
|
||||
updateModels();
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
function onInit(source) {
|
||||
@ -183,12 +183,6 @@
|
||||
var aiModelEditWindow = findIframeBySrcPart('aiModelEdit');
|
||||
if(aiModelEditWindow) {
|
||||
const providers = Object.keys(settings.providers).map(function(key) { return settings.providers[key]; });
|
||||
sendMessageToSettings({
|
||||
name: 'onProvidersUpdate',
|
||||
data: providers
|
||||
}, aiModelEditWindow.contentWindow);
|
||||
|
||||
|
||||
var model = {id: "", name: "", provider: "", capabilities: 0};
|
||||
if (message.data.model) {
|
||||
model = settings.models.find(function(model) { return model.name === message.data.model.name; });
|
||||
@ -197,6 +191,13 @@
|
||||
model : model,
|
||||
providers : providers
|
||||
}
|
||||
sendMessageToSettings({
|
||||
name: 'onProvidersUpdate',
|
||||
data: data
|
||||
}, aiModelEditWindow.contentWindow);
|
||||
|
||||
|
||||
|
||||
sendMessageToSettings({
|
||||
name: 'onModelInfo',
|
||||
data: data
|
||||
@ -209,9 +210,10 @@
|
||||
}
|
||||
break;
|
||||
case 'onDeleteAiModel':
|
||||
for (let id in settings.models) {
|
||||
if (settings.models[id].id == message.data.id) {
|
||||
delete settings.models[id];
|
||||
for (var i = 0; i < settings.models.length; i++) {
|
||||
if (settings.models[i].id == message.data.id) {
|
||||
settings.models.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
updateModels();
|
||||
@ -291,6 +293,8 @@
|
||||
// console.log('Configuration saved successfully');
|
||||
return response.json();
|
||||
}).then(function(models) {
|
||||
tempModels = models.modelsApi;
|
||||
delete models.modelsApi;
|
||||
sendMessageToSettings({
|
||||
name: 'onGetModels',
|
||||
data: models
|
||||
@ -300,6 +304,10 @@
|
||||
|
||||
function onChangeModel(data) {
|
||||
settings.providers[data.provider.name] = data.provider;
|
||||
if (tempModels) {
|
||||
settings.providers[data.provider.name].models = tempModels;
|
||||
tempModels = null;
|
||||
}
|
||||
|
||||
let isFoundModel = false;
|
||||
for(let id in settings.models) {
|
||||
|
||||
Reference in New Issue
Block a user