mirror of
https://github.com/DrizzleTime/Foxel.git
synced 2026-09-07 08:36:49 +08:00
feat(auth): add configuration options for user registration and anonymous image hosting
This commit is contained in:
@@ -9,7 +9,7 @@ using Foxel.Services.Configuration;
|
|||||||
namespace Foxel.Controllers;
|
namespace Foxel.Controllers;
|
||||||
|
|
||||||
[Route("api/auth")]
|
[Route("api/auth")]
|
||||||
public class AuthController(IAuthService authService) : BaseApiController
|
public class AuthController(IAuthService authService, IConfigService configuration) : BaseApiController
|
||||||
{
|
{
|
||||||
[HttpPost("register")]
|
[HttpPost("register")]
|
||||||
public async Task<ActionResult<BaseResult<AuthResponse>>> Register([FromBody] RegisterRequest request)
|
public async Task<ActionResult<BaseResult<AuthResponse>>> Register([FromBody] RegisterRequest request)
|
||||||
@@ -19,6 +19,13 @@ public class AuthController(IAuthService authService) : BaseApiController
|
|||||||
return Error<AuthResponse>("请求数据无效");
|
return Error<AuthResponse>("请求数据无效");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查是否允许新用户注册
|
||||||
|
var enableRegistration = configuration["AppSettings:EnableRegistration"];
|
||||||
|
if (string.Equals(enableRegistration, "false", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return Error<AuthResponse>("新用户注册功能已关闭");
|
||||||
|
}
|
||||||
|
|
||||||
var (success, message, user) = await authService.RegisterUserAsync(request);
|
var (success, message, user) = await authService.RegisterUserAsync(request);
|
||||||
if (!success)
|
if (!success)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -9,12 +9,13 @@ using Foxel.Services.Storage;
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using System.Text.Json; // Added for JsonSerializer in GetTelegramFile if it were kept
|
using System.Text.Json; // Added for JsonSerializer in GetTelegramFile if it were kept
|
||||||
|
using Foxel.Services.Configuration; // Added for IConfigService
|
||||||
|
|
||||||
namespace Foxel.Api;
|
namespace Foxel.Api;
|
||||||
|
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[Route("api/picture")]
|
[Route("api/picture")]
|
||||||
public class PictureController(IPictureService pictureService, IStorageService storageService, ILogger<PictureController> logger) : BaseApiController
|
public class PictureController(IPictureService pictureService, IStorageService storageService, ILogger<PictureController> logger, IConfigService configuration) : BaseApiController
|
||||||
{
|
{
|
||||||
private readonly ILogger<PictureController> _logger = logger;
|
private readonly ILogger<PictureController> _logger = logger;
|
||||||
|
|
||||||
@@ -74,6 +75,16 @@ public class PictureController(IPictureService pictureService, IStorageService s
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var userId = GetCurrentUserId();
|
var userId = GetCurrentUserId();
|
||||||
|
|
||||||
|
if (userId == null)
|
||||||
|
{
|
||||||
|
var enableAnonymousUpload = configuration["AppSettings:EnableAnonymousImageHosting"];
|
||||||
|
if (string.Equals(enableAnonymousUpload, "false", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return Error<PictureResponse>("匿名上传功能已关闭,请登录后操作", 403);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await using var stream = request.File.OpenReadStream();
|
await using var stream = request.File.OpenReadStream();
|
||||||
var result = await pictureService.UploadPictureAsync(
|
var result = await pictureService.UploadPictureAsync(
|
||||||
request.File.FileName,
|
request.File.FileName,
|
||||||
|
|||||||
@@ -73,6 +73,8 @@ public class DatabaseInitializer(
|
|||||||
// 其他配置
|
// 其他配置
|
||||||
["Storage:DefaultStorageModeId"] = "1",
|
["Storage:DefaultStorageModeId"] = "1",
|
||||||
["AppSettings:ServerUrl"] = "",
|
["AppSettings:ServerUrl"] = "",
|
||||||
|
["AppSettings:EnableRegistration"] = "true",
|
||||||
|
["AppSettings:EnableAnonymousImageHosting"] = "true",
|
||||||
["VectorDb:Type"] = "InMemory"
|
["VectorDb:Type"] = "InMemory"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Row, Col, Form, Input, Button, Space, Tooltip } from 'antd';
|
import { Row, Col, Form, Input, Button, Space, Tooltip, Switch } from 'antd';
|
||||||
import { LockOutlined, QuestionCircleOutlined, SaveOutlined } from '@ant-design/icons';
|
import { LockOutlined, QuestionCircleOutlined, SaveOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
interface ConfigFormItemProps {
|
interface ConfigFormItemProps {
|
||||||
@@ -23,6 +23,9 @@ const ConfigFormItem: React.FC<ConfigFormItemProps> = ({
|
|||||||
isMobile,
|
isMobile,
|
||||||
onSave,
|
onSave,
|
||||||
}) => {
|
}) => {
|
||||||
|
const booleanAppSettings = ['EnableRegistration', 'EnableAnonymousImageHosting'];
|
||||||
|
const isBooleanAppSetting = groupName === 'AppSettings' && booleanAppSettings.includes(itemKey);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row key={itemKey} gutter={isMobile ? [8, 8] : [16, 16]} align="top" style={{ marginBottom: isMobile ? 8 : 16 }}>
|
<Row key={itemKey} gutter={isMobile ? [8, 8] : [16, 16]} align="top" style={{ marginBottom: isMobile ? 8 : 16 }}>
|
||||||
<Col xs={24} sm={isMobile ? 24 : 16} md={isMobile ? 24 : 17} lg={isMobile ? 24 : 18}>
|
<Col xs={24} sm={isMobile ? 24 : 16} md={isMobile ? 24 : 17} lg={isMobile ? 24 : 18}>
|
||||||
@@ -31,7 +34,7 @@ const ConfigFormItem: React.FC<ConfigFormItemProps> = ({
|
|||||||
label={
|
label={
|
||||||
<Space align="center">
|
<Space align="center">
|
||||||
<span style={{ fontWeight: 500 }}>{itemKey}</span>
|
<span style={{ fontWeight: 500 }}>{itemKey}</span>
|
||||||
{isSecret && <LockOutlined style={{ color: '#faad14' }} />}
|
{isSecret && !isBooleanAppSetting && <LockOutlined style={{ color: '#faad14' }} />}
|
||||||
{description && (
|
{description && (
|
||||||
<Tooltip title={description}>
|
<Tooltip title={description}>
|
||||||
<QuestionCircleOutlined style={{ cursor: 'help', color: '#aaa' }} />
|
<QuestionCircleOutlined style={{ cursor: 'help', color: '#aaa' }} />
|
||||||
@@ -39,14 +42,29 @@ const ConfigFormItem: React.FC<ConfigFormItemProps> = ({
|
|||||||
)}
|
)}
|
||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
initialValue={isSecret ? '' : currentValue}
|
initialValue={
|
||||||
rules={isSecret ? [] : []}
|
isBooleanAppSetting
|
||||||
|
? currentValue === 'true'
|
||||||
|
: isSecret
|
||||||
|
? ''
|
||||||
|
: currentValue
|
||||||
|
}
|
||||||
|
valuePropName={isBooleanAppSetting ? 'checked' : undefined}
|
||||||
|
rules={isSecret || isBooleanAppSetting ? [] : []}
|
||||||
style={{ marginBottom: isMobile ? 8 : 16 }}
|
style={{ marginBottom: isMobile ? 8 : 16 }}
|
||||||
help={isSecret && currentValue ?
|
help={
|
||||||
<span style={{ fontSize: '12px', color: '#999' }}>当前已设置值。如需修改,请输入新值。</span> :
|
isBooleanAppSetting
|
||||||
(isSecret ? <span style={{ fontSize: '12px', color: '#999' }}>此为私密字段。</span> : null)}
|
? null
|
||||||
|
: isSecret && currentValue
|
||||||
|
? <span style={{ fontSize: '12px', color: '#999' }}>当前已设置值。如需修改,请输入新值。</span>
|
||||||
|
: isSecret
|
||||||
|
? <span style={{ fontSize: '12px', color: '#999' }}>此为私密字段。</span>
|
||||||
|
: null
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{isSecret ? (
|
{isBooleanAppSetting ? (
|
||||||
|
<Switch />
|
||||||
|
) : isSecret ? (
|
||||||
<Input.Password
|
<Input.Password
|
||||||
placeholder={currentValue ? '******(输入新值以更新)' : '请输入新值'}
|
placeholder={currentValue ? '******(输入新值以更新)' : '请输入新值'}
|
||||||
style={{ maxWidth: 400 }}
|
style={{ maxWidth: 400 }}
|
||||||
|
|||||||
@@ -281,13 +281,13 @@ const ConfigTabs: React.FC<ConfigTabsProps> = ({
|
|||||||
isMobile={isMobile}
|
isMobile={isMobile}
|
||||||
>
|
>
|
||||||
<Form form={formsMap.AppSettings} layout="vertical" size={isMobile ? "middle" : "large"}>
|
<Form form={formsMap.AppSettings} layout="vertical" size={isMobile ? "middle" : "large"}>
|
||||||
{renderConfigFormItems(formsMap.AppSettings, "AppSettings", ['ServerUrl', 'MaxConcurrentTasks'])}
|
{renderConfigFormItems(formsMap.AppSettings, "AppSettings", ['ServerUrl', 'MaxConcurrentTasks', 'EnableRegistration', 'EnableAnonymousImageHosting'])}
|
||||||
<Divider style={{ margin: '12px 0 20px' }} />
|
<Divider style={{ margin: '12px 0 20px' }} />
|
||||||
<Form.Item style={{ marginBottom: 0, textAlign: 'center' }}>
|
<Form.Item style={{ marginBottom: 0, textAlign: 'center' }}>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
icon={<SaveOutlined />}
|
icon={<SaveOutlined />}
|
||||||
onClick={() => onSaveAllForGroup(formsMap.AppSettings, "AppSettings", ['ServerUrl', 'MaxConcurrentTasks'])}
|
onClick={() => onSaveAllForGroup(formsMap.AppSettings, "AppSettings", ['ServerUrl', 'MaxConcurrentTasks', 'EnableRegistration', 'EnableAnonymousImageHosting'])}
|
||||||
style={{ width: isMobile ? '100%' : '240px' }}
|
style={{ width: isMobile ? '100%' : '240px' }}
|
||||||
>
|
>
|
||||||
保存所有应用设置
|
保存所有应用设置
|
||||||
@@ -309,43 +309,47 @@ const ConfigTabs: React.FC<ConfigTabsProps> = ({
|
|||||||
description="配置文件上传处理方式和图片转换参数"
|
description="配置文件上传处理方式和图片转换参数"
|
||||||
isMobile={isMobile}
|
isMobile={isMobile}
|
||||||
>
|
>
|
||||||
|
<Form form={formsMap.Upload} layout="vertical" size={isMobile ? "middle" : "large"}>
|
||||||
<div style={{
|
<div style={{
|
||||||
display: 'grid',
|
display: 'grid',
|
||||||
gridTemplateColumns: '1fr',
|
gridTemplateColumns: '1fr',
|
||||||
gap: isMobile ? 20 : 24,
|
gap: isMobile ? 20 : 24,
|
||||||
marginBottom: 0
|
marginBottom: 0
|
||||||
}}>
|
}}>
|
||||||
<div>
|
<Form.Item
|
||||||
<div style={{ marginBottom: 8, fontSize: 14, fontWeight: 500, color: '#666' }}>
|
name="ThumbnailMaxWidth"
|
||||||
|
label={
|
||||||
|
<div style={{ fontSize: 14, fontWeight: 500, color: '#666' }}>
|
||||||
缩略图最大宽度 (px)
|
缩略图最大宽度 (px)
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
|
help={<div style={{ fontSize: 12, color: '#999', marginTop: 0 }}>{allDescriptions.Upload?.ThumbnailMaxWidth}</div>}
|
||||||
|
initialValue={parseInt(configs.Upload?.ThumbnailMaxWidth || '400', 10)}
|
||||||
|
style={{ marginBottom: 0 }}
|
||||||
|
>
|
||||||
<InputNumber
|
<InputNumber
|
||||||
min={100}
|
min={100}
|
||||||
max={1000}
|
max={1000}
|
||||||
step={50}
|
step={50}
|
||||||
value={parseInt(configs.Upload?.ThumbnailMaxWidth || '400', 10)}
|
|
||||||
onChange={(value) => {
|
|
||||||
if (value !== null) {
|
|
||||||
onBaseSaveConfig('Upload', 'ThumbnailMaxWidth', value.toString())
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
/>
|
/>
|
||||||
<div style={{ fontSize: 12, color: '#999', marginTop: 4 }}>
|
</Form.Item>
|
||||||
{allDescriptions.Upload?.ThumbnailMaxWidth}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
<Form.Item
|
||||||
<div style={{ marginBottom: 8, fontSize: 14, fontWeight: 500, color: '#666' }}>
|
name="ThumbnailCompressionQuality"
|
||||||
|
label={
|
||||||
|
<div style={{ fontSize: 14, fontWeight: 500, color: '#666' }}>
|
||||||
缩略图压缩质量
|
缩略图压缩质量
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
|
help={<div style={{ fontSize: 12, color: '#999', marginTop: isMobile ? 8 : 16, textAlign: 'center' }}>{allDescriptions.Upload?.ThumbnailCompressionQuality}</div>}
|
||||||
|
initialValue={parseInt(configs.Upload?.ThumbnailCompressionQuality || '75', 10)}
|
||||||
|
style={{ marginBottom: 0 }}
|
||||||
|
>
|
||||||
<Slider
|
<Slider
|
||||||
min={30}
|
min={30}
|
||||||
max={90}
|
max={90}
|
||||||
step={5}
|
step={5}
|
||||||
value={parseInt(configs.Upload?.ThumbnailCompressionQuality || '75', 10)}
|
|
||||||
onChange={(value) => onBaseSaveConfig('Upload', 'ThumbnailCompressionQuality', value.toString())}
|
|
||||||
style={{ margin: isMobile ? '0 5px' : '0 10px' }}
|
style={{ margin: isMobile ? '0 5px' : '0 10px' }}
|
||||||
tooltip={{
|
tooltip={{
|
||||||
formatter: value => `${value}%`
|
formatter: value => `${value}%`
|
||||||
@@ -356,21 +360,23 @@ const ConfigTabs: React.FC<ConfigTabsProps> = ({
|
|||||||
90: '90%'
|
90: '90%'
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div style={{ fontSize: 12, color: '#999', marginTop: 16, textAlign: 'center' }}>
|
</Form.Item>
|
||||||
{allDescriptions.Upload?.ThumbnailCompressionQuality}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
<Form.Item
|
||||||
<div style={{ marginBottom: 8, fontSize: 14, fontWeight: 500, color: '#666' }}>
|
name="HighQualityImageCompressionQuality"
|
||||||
|
label={
|
||||||
|
<div style={{ fontSize: 14, fontWeight: 500, color: '#666' }}>
|
||||||
高清图片压缩质量
|
高清图片压缩质量
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
|
help={<div style={{ fontSize: 12, color: '#999', marginTop: isMobile ? 8 : 16, textAlign: 'center' }}>{allDescriptions.Upload?.HighQualityImageCompressionQuality}</div>}
|
||||||
|
initialValue={parseInt(configs.Upload?.HighQualityImageCompressionQuality || '95', 10)}
|
||||||
|
style={{ marginBottom: 0 }}
|
||||||
|
>
|
||||||
<Slider
|
<Slider
|
||||||
min={50}
|
min={50}
|
||||||
max={100}
|
max={100}
|
||||||
step={5}
|
step={5}
|
||||||
value={parseInt(configs.Upload?.HighQualityImageCompressionQuality || '95', 10)}
|
|
||||||
onChange={(value) => onBaseSaveConfig('Upload', 'HighQualityImageCompressionQuality', value.toString())}
|
|
||||||
style={{ margin: isMobile ? '0 5px' : '0 10px' }}
|
style={{ margin: isMobile ? '0 5px' : '0 10px' }}
|
||||||
tooltip={{
|
tooltip={{
|
||||||
formatter: value => `${value}%`
|
formatter: value => `${value}%`
|
||||||
@@ -381,11 +387,20 @@ const ConfigTabs: React.FC<ConfigTabsProps> = ({
|
|||||||
100: '100%'
|
100: '100%'
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div style={{ fontSize: 12, color: '#999', marginTop: 16, textAlign: 'center' }}>
|
</Form.Item>
|
||||||
{allDescriptions.Upload?.HighQualityImageCompressionQuality}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<Divider style={{ margin: '24px 0 20px' }} />
|
||||||
|
<Form.Item style={{ marginBottom: 0, textAlign: 'center' }}>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
icon={<SaveOutlined />}
|
||||||
|
onClick={() => onSaveAllForGroup(formsMap.Upload, "Upload", ['ThumbnailMaxWidth', 'ThumbnailCompressionQuality', 'HighQualityImageCompressionQuality'])}
|
||||||
|
style={{ width: isMobile ? '100%' : '240px' }}
|
||||||
|
>
|
||||||
|
保存所有上传设置
|
||||||
|
</Button>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
</ConfigSection>
|
</ConfigSection>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -43,7 +43,9 @@ const allDescriptions: Record<string, Record<string, string>> = {
|
|||||||
},
|
},
|
||||||
AppSettings: {
|
AppSettings: {
|
||||||
ServerUrl: '服务器URL',
|
ServerUrl: '服务器URL',
|
||||||
MaxConcurrentTasks: '后台任务最大并发处理数量 (例如: 图像分析、标签生成等)'
|
MaxConcurrentTasks: '后台任务最大并发处理数量 (例如: 图像分析、标签生成等)',
|
||||||
|
EnableRegistration: '是否允许新用户注册 (true/false)',
|
||||||
|
EnableAnonymousImageHosting: '是否允许匿名用户上传图片 (true/false)'
|
||||||
},
|
},
|
||||||
Upload: {
|
Upload: {
|
||||||
HighQualityImageCompressionQuality: '高清图片的压缩质量,越高图片质量越好但文件越大。范围 50-100。',
|
HighQualityImageCompressionQuality: '高清图片的压缩质量,越高图片质量越好但文件越大。范围 50-100。',
|
||||||
@@ -52,6 +54,7 @@ const allDescriptions: Record<string, Record<string, string>> = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const booleanAppSettings = ['EnableRegistration', 'EnableAnonymousImageHosting'];
|
||||||
|
|
||||||
const System: React.FC = () => {
|
const System: React.FC = () => {
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
@@ -115,9 +118,13 @@ const System: React.FC = () => {
|
|||||||
const formInstance = formsMap[formInstanceKey];
|
const formInstance = formsMap[formInstanceKey];
|
||||||
|
|
||||||
if (formInstance) {
|
if (formInstance) {
|
||||||
const initialGroupValues: Record<string, string> = {};
|
const initialGroupValues: Record<string, any> = {}; // Changed to any for boolean values
|
||||||
Object.keys(configGroups[group]).forEach(key => {
|
Object.keys(configGroups[group]).forEach(key => {
|
||||||
if (!secretFieldsMap[group]?.includes(key)) {
|
if (group === 'AppSettings' && booleanAppSettings.includes(key)) {
|
||||||
|
initialGroupValues[key] = configGroups[group][key] === 'true';
|
||||||
|
} else if (group === 'Upload' && ['ThumbnailMaxWidth', 'ThumbnailCompressionQuality', 'HighQualityImageCompressionQuality'].includes(key)) {
|
||||||
|
initialGroupValues[key] = parseInt(configGroups[group][key] || (key === 'ThumbnailMaxWidth' ? '400' : (key === 'ThumbnailCompressionQuality' ? '75' : '95')), 10);
|
||||||
|
} else if (!secretFieldsMap[group]?.includes(key)) {
|
||||||
initialGroupValues[key] = configGroups[group][key];
|
initialGroupValues[key] = configGroups[group][key];
|
||||||
} else {
|
} else {
|
||||||
initialGroupValues[key] = '';
|
initialGroupValues[key] = '';
|
||||||
@@ -211,9 +218,13 @@ const System: React.FC = () => {
|
|||||||
const handleSaveSingleConfig = async (formInstance: any, groupName: string, key: string) => {
|
const handleSaveSingleConfig = async (formInstance: any, groupName: string, key: string) => {
|
||||||
try {
|
try {
|
||||||
await formInstance.validateFields([key]);
|
await formInstance.validateFields([key]);
|
||||||
const value = formInstance.getFieldValue(key);
|
let value = formInstance.getFieldValue(key);
|
||||||
const isSecret = secretFields[groupName]?.includes(key);
|
const isSecret = secretFields[groupName]?.includes(key);
|
||||||
|
|
||||||
|
if (groupName === 'AppSettings' && booleanAppSettings.includes(key) && typeof value === 'boolean') {
|
||||||
|
value = String(value);
|
||||||
|
}
|
||||||
|
|
||||||
if (isSecret && (value === '' || value === undefined)) {
|
if (isSecret && (value === '' || value === undefined)) {
|
||||||
message.info(`未输入 ${key} 的新值,不作更改。`);
|
message.info(`未输入 ${key} 的新值,不作更改。`);
|
||||||
return;
|
return;
|
||||||
@@ -242,7 +253,12 @@ const System: React.FC = () => {
|
|||||||
|
|
||||||
// 计算需要保存的总数
|
// 计算需要保存的总数
|
||||||
for (const key of itemKeys) {
|
for (const key of itemKeys) {
|
||||||
const value = values[key];
|
let value = values[key];
|
||||||
|
if (groupName === 'AppSettings' && booleanAppSettings.includes(key) && typeof value === 'boolean') {
|
||||||
|
value = String(value);
|
||||||
|
} else if (groupName === 'Upload' && typeof value === 'number') { // Ensure numbers are converted to strings for saving
|
||||||
|
value = String(value);
|
||||||
|
}
|
||||||
const isSecret = secretFields[groupName]?.includes(key);
|
const isSecret = secretFields[groupName]?.includes(key);
|
||||||
if (!(isSecret && (value === '' || value === undefined)) &&
|
if (!(isSecret && (value === '' || value === undefined)) &&
|
||||||
(isSecret || configs[groupName]?.[key] !== value)) {
|
(isSecret || configs[groupName]?.[key] !== value)) {
|
||||||
@@ -265,9 +281,15 @@ const System: React.FC = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
for (const key of itemKeys) {
|
for (const key of itemKeys) {
|
||||||
const value = values[key];
|
let value = values[key];
|
||||||
const isSecret = secretFields[groupName]?.includes(key);
|
const isSecret = secretFields[groupName]?.includes(key);
|
||||||
|
|
||||||
|
if (groupName === 'AppSettings' && booleanAppSettings.includes(key) && typeof value === 'boolean') {
|
||||||
|
value = String(value);
|
||||||
|
} else if (groupName === 'Upload' && typeof value === 'number') { // Ensure numbers are converted to strings for saving
|
||||||
|
value = String(value);
|
||||||
|
}
|
||||||
|
|
||||||
if (isSecret && (value === '' || value === undefined)) {
|
if (isSecret && (value === '' || value === undefined)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user