Commit be567ef7 by Seefs Committed by GitHub

fix: fix model deployment style issues, lint problems, and i18n gaps. (#2556)

* fix: fix model deployment style issues, lint problems, and i18n gaps.

* fix: adjust the key not to be displayed on the frontend, tested via the backend.

* fix: adjust the sidebar configuration logic to use the default configuration items if they are not defined.
parent 1c95a9fe
package controller package controller
import ( import (
"bytes"
"encoding/json"
"fmt" "fmt"
"strconv" "strconv"
"strings" "strings"
...@@ -23,6 +25,20 @@ func getIoAPIKey(c *gin.Context) (string, bool) { ...@@ -23,6 +25,20 @@ func getIoAPIKey(c *gin.Context) (string, bool) {
return apiKey, true return apiKey, true
} }
func GetModelDeploymentSettings(c *gin.Context) {
common.OptionMapRWMutex.RLock()
enabled := common.OptionMap["model_deployment.ionet.enabled"] == "true"
hasAPIKey := strings.TrimSpace(common.OptionMap["model_deployment.ionet.api_key"]) != ""
common.OptionMapRWMutex.RUnlock()
common.ApiSuccess(c, gin.H{
"provider": "io.net",
"enabled": enabled,
"configured": hasAPIKey,
"can_connect": enabled && hasAPIKey,
})
}
func getIoClient(c *gin.Context) (*ionet.Client, bool) { func getIoClient(c *gin.Context) (*ionet.Client, bool) {
apiKey, ok := getIoAPIKey(c) apiKey, ok := getIoAPIKey(c)
if !ok { if !ok {
...@@ -44,15 +60,28 @@ func TestIoNetConnection(c *gin.Context) { ...@@ -44,15 +60,28 @@ func TestIoNetConnection(c *gin.Context) {
APIKey string `json:"api_key"` APIKey string `json:"api_key"`
} }
if err := c.ShouldBindJSON(&req); err != nil { rawBody, err := c.GetRawData()
common.ApiErrorMsg(c, "invalid request payload") if err != nil {
common.ApiError(c, err)
return return
} }
if len(bytes.TrimSpace(rawBody)) > 0 {
if err := json.Unmarshal(rawBody, &req); err != nil {
common.ApiErrorMsg(c, "invalid request payload")
return
}
}
apiKey := strings.TrimSpace(req.APIKey) apiKey := strings.TrimSpace(req.APIKey)
if apiKey == "" { if apiKey == "" {
common.ApiErrorMsg(c, "api_key is required") common.OptionMapRWMutex.RLock()
return storedKey := strings.TrimSpace(common.OptionMap["model_deployment.ionet.api_key"])
common.OptionMapRWMutex.RUnlock()
if storedKey == "" {
common.ApiErrorMsg(c, "api_key is required")
return
}
apiKey = storedKey
} }
client := ionet.NewEnterpriseClient(apiKey) client := ionet.NewEnterpriseClient(apiKey)
......
...@@ -20,7 +20,11 @@ func GetOptions(c *gin.Context) { ...@@ -20,7 +20,11 @@ func GetOptions(c *gin.Context) {
var options []*model.Option var options []*model.Option
common.OptionMapRWMutex.Lock() common.OptionMapRWMutex.Lock()
for k, v := range common.OptionMap { for k, v := range common.OptionMap {
if strings.HasSuffix(k, "Token") || strings.HasSuffix(k, "Secret") || strings.HasSuffix(k, "Key") { if strings.HasSuffix(k, "Token") ||
strings.HasSuffix(k, "Secret") ||
strings.HasSuffix(k, "Key") ||
strings.HasSuffix(k, "secret") ||
strings.HasSuffix(k, "api_key") {
continue continue
} }
options = append(options, &model.Option{ options = append(options, &model.Option{
......
...@@ -269,24 +269,18 @@ func SetApiRouter(router *gin.Engine) { ...@@ -269,24 +269,18 @@ func SetApiRouter(router *gin.Engine) {
deploymentsRoute := apiRouter.Group("/deployments") deploymentsRoute := apiRouter.Group("/deployments")
deploymentsRoute.Use(middleware.AdminAuth()) deploymentsRoute.Use(middleware.AdminAuth())
{ {
// List and search deployments deploymentsRoute.GET("/settings", controller.GetModelDeploymentSettings)
deploymentsRoute.POST("/settings/test-connection", controller.TestIoNetConnection)
deploymentsRoute.GET("/", controller.GetAllDeployments) deploymentsRoute.GET("/", controller.GetAllDeployments)
deploymentsRoute.GET("/search", controller.SearchDeployments) deploymentsRoute.GET("/search", controller.SearchDeployments)
// Connection utilities
deploymentsRoute.POST("/test-connection", controller.TestIoNetConnection) deploymentsRoute.POST("/test-connection", controller.TestIoNetConnection)
// Resource and configuration endpoints
deploymentsRoute.GET("/hardware-types", controller.GetHardwareTypes) deploymentsRoute.GET("/hardware-types", controller.GetHardwareTypes)
deploymentsRoute.GET("/locations", controller.GetLocations) deploymentsRoute.GET("/locations", controller.GetLocations)
deploymentsRoute.GET("/available-replicas", controller.GetAvailableReplicas) deploymentsRoute.GET("/available-replicas", controller.GetAvailableReplicas)
deploymentsRoute.POST("/price-estimation", controller.GetPriceEstimation) deploymentsRoute.POST("/price-estimation", controller.GetPriceEstimation)
deploymentsRoute.GET("/check-name", controller.CheckClusterNameAvailability) deploymentsRoute.GET("/check-name", controller.CheckClusterNameAvailability)
// Create new deployment
deploymentsRoute.POST("/", controller.CreateDeployment) deploymentsRoute.POST("/", controller.CreateDeployment)
// Individual deployment operations
deploymentsRoute.GET("/:id", controller.GetDeployment) deploymentsRoute.GET("/:id", controller.GetDeployment)
deploymentsRoute.GET("/:id/logs", controller.GetDeploymentLogs) deploymentsRoute.GET("/:id/logs", controller.GetDeploymentLogs)
deploymentsRoute.GET("/:id/containers", controller.ListDeploymentContainers) deploymentsRoute.GET("/:id/containers", controller.ListDeploymentContainers)
...@@ -295,14 +289,6 @@ func SetApiRouter(router *gin.Engine) { ...@@ -295,14 +289,6 @@ func SetApiRouter(router *gin.Engine) {
deploymentsRoute.PUT("/:id/name", controller.UpdateDeploymentName) deploymentsRoute.PUT("/:id/name", controller.UpdateDeploymentName)
deploymentsRoute.POST("/:id/extend", controller.ExtendDeployment) deploymentsRoute.POST("/:id/extend", controller.ExtendDeployment)
deploymentsRoute.DELETE("/:id", controller.DeleteDeployment) deploymentsRoute.DELETE("/:id", controller.DeleteDeployment)
// Future batch operations:
// deploymentsRoute.POST("/:id/start", controller.StartDeployment)
// deploymentsRoute.POST("/:id/stop", controller.StopDeployment)
// deploymentsRoute.POST("/:id/restart", controller.RestartDeployment)
// deploymentsRoute.POST("/batch_delete", controller.BatchDeleteDeployments)
// deploymentsRoute.POST("/batch_start", controller.BatchStartDeployments)
// deploymentsRoute.POST("/batch_stop", controller.BatchStopDeployments)
} }
} }
} }
...@@ -25,7 +25,9 @@ export default defineConfig({ ...@@ -25,7 +25,9 @@ export default defineConfig({
"zh", "zh",
"en", "en",
"fr", "fr",
"ru" "ru",
"ja",
"vi"
], ],
extract: { extract: {
input: [ input: [
......
...@@ -44,7 +44,10 @@ import CodeViewer from '../../../playground/CodeViewer'; ...@@ -44,7 +44,10 @@ import CodeViewer from '../../../playground/CodeViewer';
import { StatusContext } from '../../../../context/Status'; import { StatusContext } from '../../../../context/Status';
import { UserContext } from '../../../../context/User'; import { UserContext } from '../../../../context/User';
import { useUserPermissions } from '../../../../hooks/common/useUserPermissions'; import { useUserPermissions } from '../../../../hooks/common/useUserPermissions';
import { useSidebar } from '../../../../hooks/common/useSidebar'; import {
mergeAdminConfig,
useSidebar,
} from '../../../../hooks/common/useSidebar';
const NotificationSettings = ({ const NotificationSettings = ({
t, t,
...@@ -82,6 +85,7 @@ const NotificationSettings = ({ ...@@ -82,6 +85,7 @@ const NotificationSettings = ({
enabled: true, enabled: true,
channel: true, channel: true,
models: true, models: true,
deployment: true,
redemption: true, redemption: true,
user: true, user: true,
setting: true, setting: true,
...@@ -164,6 +168,7 @@ const NotificationSettings = ({ ...@@ -164,6 +168,7 @@ const NotificationSettings = ({
enabled: true, enabled: true,
channel: true, channel: true,
models: true, models: true,
deployment: true,
redemption: true, redemption: true,
user: true, user: true,
setting: true, setting: true,
...@@ -178,14 +183,27 @@ const NotificationSettings = ({ ...@@ -178,14 +183,27 @@ const NotificationSettings = ({
try { try {
// 获取管理员全局配置 // 获取管理员全局配置
if (statusState?.status?.SidebarModulesAdmin) { if (statusState?.status?.SidebarModulesAdmin) {
const adminConf = JSON.parse(statusState.status.SidebarModulesAdmin); try {
setAdminConfig(adminConf); const adminConf = JSON.parse(
statusState.status.SidebarModulesAdmin,
);
setAdminConfig(mergeAdminConfig(adminConf));
} catch (error) {
setAdminConfig(mergeAdminConfig(null));
}
} else {
setAdminConfig(mergeAdminConfig(null));
} }
// 获取用户个人配置 // 获取用户个人配置
const userRes = await API.get('/api/user/self'); const userRes = await API.get('/api/user/self');
if (userRes.data.success && userRes.data.data.sidebar_modules) { if (userRes.data.success && userRes.data.data.sidebar_modules) {
const userConf = JSON.parse(userRes.data.data.sidebar_modules); let userConf;
if (typeof userRes.data.data.sidebar_modules === 'string') {
userConf = JSON.parse(userRes.data.data.sidebar_modules);
} else {
userConf = userRes.data.data.sidebar_modules;
}
setSidebarModulesUser(userConf); setSidebarModulesUser(userConf);
} }
} catch (error) { } catch (error) {
...@@ -274,6 +292,11 @@ const NotificationSettings = ({ ...@@ -274,6 +292,11 @@ const NotificationSettings = ({
{ key: 'channel', title: t('渠道管理'), description: t('API渠道配置') }, { key: 'channel', title: t('渠道管理'), description: t('API渠道配置') },
{ key: 'models', title: t('模型管理'), description: t('AI模型配置') }, { key: 'models', title: t('模型管理'), description: t('AI模型配置') },
{ {
key: 'deployment',
title: t('模型部署'),
description: t('模型部署管理'),
},
{
key: 'redemption', key: 'redemption',
title: t('兑换码管理'), title: t('兑换码管理'),
description: t('兑换码生成管理'), description: t('兑换码生成管理'),
...@@ -812,7 +835,9 @@ const NotificationSettings = ({ ...@@ -812,7 +835,9 @@ const NotificationSettings = ({
</Typography.Text> </Typography.Text>
</div> </div>
<Switch <Switch
checked={sidebarModulesUser[section.key]?.enabled} checked={
sidebarModulesUser[section.key]?.enabled !== false
}
onChange={handleSectionChange(section.key)} onChange={handleSectionChange(section.key)}
size='default' size='default'
/> />
...@@ -835,7 +860,8 @@ const NotificationSettings = ({ ...@@ -835,7 +860,8 @@ const NotificationSettings = ({
> >
<Card <Card
className={`!rounded-xl border border-gray-200 hover:border-blue-300 transition-all duration-200 ${ className={`!rounded-xl border border-gray-200 hover:border-blue-300 transition-all duration-200 ${
sidebarModulesUser[section.key]?.enabled sidebarModulesUser[section.key]?.enabled !==
false
? '' ? ''
: 'opacity-50' : 'opacity-50'
}`} }`}
...@@ -866,7 +892,7 @@ const NotificationSettings = ({ ...@@ -866,7 +892,7 @@ const NotificationSettings = ({
checked={ checked={
sidebarModulesUser[section.key]?.[ sidebarModulesUser[section.key]?.[
module.key module.key
] ] !== false
} }
onChange={handleModuleChange( onChange={handleModuleChange(
section.key, section.key,
...@@ -874,8 +900,8 @@ const NotificationSettings = ({ ...@@ -874,8 +900,8 @@ const NotificationSettings = ({
)} )}
size='default' size='default'
disabled={ disabled={
!sidebarModulesUser[section.key] sidebarModulesUser[section.key]
?.enabled ?.enabled === false
} }
/> />
</div> </div>
......
...@@ -27,13 +27,14 @@ const DeploymentsActions = ({ ...@@ -27,13 +27,14 @@ const DeploymentsActions = ({
setEditingDeployment, setEditingDeployment,
setShowEdit, setShowEdit,
batchDeleteDeployments, batchDeleteDeployments,
batchOperationsEnabled = true,
compactMode, compactMode,
setCompactMode, setCompactMode,
showCreateModal, showCreateModal,
setShowCreateModal, setShowCreateModal,
t, t,
}) => { }) => {
const hasSelected = selectedKeys.length > 0; const hasSelected = batchOperationsEnabled && selectedKeys.length > 0;
const handleAddDeployment = () => { const handleAddDeployment = () => {
if (setShowCreateModal) { if (setShowCreateModal) {
...@@ -53,7 +54,6 @@ const DeploymentsActions = ({ ...@@ -53,7 +54,6 @@ const DeploymentsActions = ({
setSelectedKeys([]); setSelectedKeys([]);
}; };
return ( return (
<div className='flex flex-wrap gap-2 w-full md:w-auto order-2 md:order-1'> <div className='flex flex-wrap gap-2 w-full md:w-auto order-2 md:order-1'>
<Button <Button
......
...@@ -43,7 +43,8 @@ const DeploymentsTable = (deploymentsData) => { ...@@ -43,7 +43,8 @@ const DeploymentsTable = (deploymentsData) => {
deploymentCount, deploymentCount,
compactMode, compactMode,
visibleColumns, visibleColumns,
setSelectedKeys, rowSelection,
batchOperationsEnabled = true,
handlePageChange, handlePageChange,
handlePageSizeChange, handlePageSizeChange,
handleRow, handleRow,
...@@ -95,7 +96,10 @@ const DeploymentsTable = (deploymentsData) => { ...@@ -95,7 +96,10 @@ const DeploymentsTable = (deploymentsData) => {
}; };
const handleConfirmAction = () => { const handleConfirmAction = () => {
if (selectedDeployment && confirmOperation === 'delete') { if (
selectedDeployment &&
(confirmOperation === 'delete' || confirmOperation === 'destroy')
) {
deleteDeployment(selectedDeployment.id); deleteDeployment(selectedDeployment.id);
} }
setShowConfirmDialog(false); setShowConfirmDialog(false);
...@@ -179,11 +183,7 @@ const DeploymentsTable = (deploymentsData) => { ...@@ -179,11 +183,7 @@ const DeploymentsTable = (deploymentsData) => {
hidePagination={true} hidePagination={true}
expandAllRows={false} expandAllRows={false}
onRow={handleRow} onRow={handleRow}
rowSelection={{ rowSelection={batchOperationsEnabled ? rowSelection : undefined}
onChange: (selectedRowKeys, selectedRows) => {
setSelectedKeys(selectedRows);
},
}}
empty={ empty={
<Empty <Empty
image={<IllustrationNoResult style={{ width: 150, height: 150 }} />} image={<IllustrationNoResult style={{ width: 150, height: 150 }} />}
...@@ -235,7 +235,7 @@ const DeploymentsTable = (deploymentsData) => { ...@@ -235,7 +235,7 @@ const DeploymentsTable = (deploymentsData) => {
onCancel={() => setShowConfirmDialog(false)} onCancel={() => setShowConfirmDialog(false)}
onConfirm={handleConfirmAction} onConfirm={handleConfirmAction}
title={t('确认操作')} title={t('确认操作')}
type="danger" type='danger'
deployment={selectedDeployment} deployment={selectedDeployment}
operation={confirmOperation} operation={confirmOperation}
t={t} t={t}
......
...@@ -32,9 +32,10 @@ import { createCardProPagination } from '../../../helpers/utils'; ...@@ -32,9 +32,10 @@ import { createCardProPagination } from '../../../helpers/utils';
const DeploymentsPage = () => { const DeploymentsPage = () => {
const deploymentsData = useDeploymentsData(); const deploymentsData = useDeploymentsData();
const isMobile = useIsMobile(); const isMobile = useIsMobile();
// Create deployment modal state // Create deployment modal state
const [showCreateModal, setShowCreateModal] = useState(false); const [showCreateModal, setShowCreateModal] = useState(false);
const batchOperationsEnabled = false;
const { const {
// Edit state // Edit state
...@@ -81,7 +82,7 @@ const DeploymentsPage = () => { ...@@ -81,7 +82,7 @@ const DeploymentsPage = () => {
visible={showEdit} visible={showEdit}
handleClose={closeEdit} handleClose={closeEdit}
/> />
<CreateDeploymentModal <CreateDeploymentModal
visible={showCreateModal} visible={showCreateModal}
onCancel={() => setShowCreateModal(false)} onCancel={() => setShowCreateModal(false)}
...@@ -109,6 +110,7 @@ const DeploymentsPage = () => { ...@@ -109,6 +110,7 @@ const DeploymentsPage = () => {
setEditingDeployment={setEditingDeployment} setEditingDeployment={setEditingDeployment}
setShowEdit={setShowEdit} setShowEdit={setShowEdit}
batchDeleteDeployments={batchDeleteDeployments} batchDeleteDeployments={batchDeleteDeployments}
batchOperationsEnabled={batchOperationsEnabled}
compactMode={compactMode} compactMode={compactMode}
setCompactMode={setCompactMode} setCompactMode={setCompactMode}
showCreateModal={showCreateModal} showCreateModal={showCreateModal}
...@@ -138,7 +140,10 @@ const DeploymentsPage = () => { ...@@ -138,7 +140,10 @@ const DeploymentsPage = () => {
})} })}
t={deploymentsData.t} t={deploymentsData.t}
> >
<DeploymentsTable {...deploymentsData} /> <DeploymentsTable
{...deploymentsData}
batchOperationsEnabled={batchOperationsEnabled}
/>
</CardPro> </CardPro>
</> </>
); );
......
...@@ -25,6 +25,56 @@ import { API } from '../../helpers'; ...@@ -25,6 +25,56 @@ import { API } from '../../helpers';
const sidebarEventTarget = new EventTarget(); const sidebarEventTarget = new EventTarget();
const SIDEBAR_REFRESH_EVENT = 'sidebar-refresh'; const SIDEBAR_REFRESH_EVENT = 'sidebar-refresh';
export const DEFAULT_ADMIN_CONFIG = {
chat: {
enabled: true,
playground: true,
chat: true,
},
console: {
enabled: true,
detail: true,
token: true,
log: true,
midjourney: true,
task: true,
},
personal: {
enabled: true,
topup: true,
personal: true,
},
admin: {
enabled: true,
channel: true,
models: true,
deployment: true,
redemption: true,
user: true,
setting: true,
},
};
const deepClone = (value) => JSON.parse(JSON.stringify(value));
export const mergeAdminConfig = (savedConfig) => {
const merged = deepClone(DEFAULT_ADMIN_CONFIG);
if (!savedConfig || typeof savedConfig !== 'object') return merged;
for (const [sectionKey, sectionConfig] of Object.entries(savedConfig)) {
if (!sectionConfig || typeof sectionConfig !== 'object') continue;
if (!merged[sectionKey]) {
merged[sectionKey] = { ...sectionConfig };
continue;
}
merged[sectionKey] = { ...merged[sectionKey], ...sectionConfig };
}
return merged;
};
export const useSidebar = () => { export const useSidebar = () => {
const [statusState] = useContext(StatusContext); const [statusState] = useContext(StatusContext);
const [userConfig, setUserConfig] = useState(null); const [userConfig, setUserConfig] = useState(null);
...@@ -37,48 +87,17 @@ export const useSidebar = () => { ...@@ -37,48 +87,17 @@ export const useSidebar = () => {
instanceIdRef.current = `sidebar-${Date.now()}-${randomPart}`; instanceIdRef.current = `sidebar-${Date.now()}-${randomPart}`;
} }
// 默认配置
const defaultAdminConfig = {
chat: {
enabled: true,
playground: true,
chat: true,
},
console: {
enabled: true,
detail: true,
token: true,
log: true,
midjourney: true,
task: true,
},
personal: {
enabled: true,
topup: true,
personal: true,
},
admin: {
enabled: true,
channel: true,
models: true,
deployment: true,
redemption: true,
user: true,
setting: true,
},
};
// 获取管理员配置 // 获取管理员配置
const adminConfig = useMemo(() => { const adminConfig = useMemo(() => {
if (statusState?.status?.SidebarModulesAdmin) { if (statusState?.status?.SidebarModulesAdmin) {
try { try {
const config = JSON.parse(statusState.status.SidebarModulesAdmin); const config = JSON.parse(statusState.status.SidebarModulesAdmin);
return config; return mergeAdminConfig(config);
} catch (error) { } catch (error) {
return defaultAdminConfig; return mergeAdminConfig(null);
} }
} }
return defaultAdminConfig; return mergeAdminConfig(null);
}, [statusState?.status?.SidebarModulesAdmin]); }, [statusState?.status?.SidebarModulesAdmin]);
// 加载用户配置的通用方法 // 加载用户配置的通用方法
......
...@@ -39,10 +39,13 @@ export const useDeploymentResources = () => { ...@@ -39,10 +39,13 @@ export const useDeploymentResources = () => {
setLoadingHardware(true); setLoadingHardware(true);
const response = await API.get('/api/deployments/hardware-types'); const response = await API.get('/api/deployments/hardware-types');
if (response.data.success) { if (response.data.success) {
const { hardware_types: hardwareList = [], total_available } = response.data.data || {}; const { hardware_types: hardwareList = [], total_available } =
response.data.data || {};
const normalizedHardware = hardwareList.map((hardware) => { const normalizedHardware = hardwareList.map((hardware) => {
const availableCountValue = Number(hardware.available_count); const availableCountValue = Number(hardware.available_count);
const availableCount = Number.isNaN(availableCountValue) ? 0 : availableCountValue; const availableCount = Number.isNaN(availableCountValue)
? 0
: availableCountValue;
const availableBool = const availableBool =
typeof hardware.available === 'boolean' typeof hardware.available === 'boolean'
? hardware.available ? hardware.available
...@@ -57,7 +60,9 @@ export const useDeploymentResources = () => { ...@@ -57,7 +60,9 @@ export const useDeploymentResources = () => {
const providedTotal = Number(total_available); const providedTotal = Number(total_available);
const fallbackTotal = normalizedHardware.reduce( const fallbackTotal = normalizedHardware.reduce(
(acc, item) => acc + (Number.isNaN(item.available_count) ? 0 : item.available_count), (acc, item) =>
acc +
(Number.isNaN(item.available_count) ? 0 : item.available_count),
0, 0,
); );
const hasProvidedTotal = const hasProvidedTotal =
...@@ -85,37 +90,64 @@ export const useDeploymentResources = () => { ...@@ -85,37 +90,64 @@ export const useDeploymentResources = () => {
} }
}, []); }, []);
const fetchLocations = useCallback(async () => { const fetchLocations = useCallback(async (hardwareId, gpuCount = 1) => {
if (!hardwareId) {
setLocations([]);
setLocationsTotalAvailable(0);
return [];
}
try { try {
setLoadingLocations(true); setLoadingLocations(true);
const response = await API.get('/api/deployments/locations'); const response = await API.get(
`/api/deployments/available-replicas?hardware_id=${hardwareId}&gpu_count=${gpuCount}`,
);
if (response.data.success) { if (response.data.success) {
const { locations: locationsList = [], total } = response.data.data || {}; const replicas = response.data.data?.replicas || [];
const normalizedLocations = locationsList.map((location) => { const nextLocationsMap = new Map();
const iso2 = (location.iso2 || '').toString().toUpperCase(); replicas.forEach((replica) => {
const availableValue = Number(location.available); const rawId = replica?.location_id ?? replica?.location?.id;
const available = Number.isNaN(availableValue) ? 0 : availableValue; if (rawId === null || rawId === undefined) return;
return { const mapKey = String(rawId);
...location, if (nextLocationsMap.has(mapKey)) return;
const rawIso2 =
replica?.iso2 ?? replica?.location_iso2 ?? replica?.location?.iso2;
const iso2 = rawIso2 ? String(rawIso2).toUpperCase() : '';
const name =
replica?.location_name ??
replica?.location?.name ??
replica?.name ??
String(rawId);
nextLocationsMap.set(mapKey, {
id: rawId,
name: String(name),
iso2, iso2,
available, region:
}; replica?.region ??
replica?.location_region ??
replica?.location?.region,
country:
replica?.country ??
replica?.location_country ??
replica?.location?.country,
code:
replica?.code ??
replica?.location_code ??
replica?.location?.code,
available: Number(replica?.available_count) || 0,
});
}); });
const providedTotal = Number(total);
const fallbackTotal = normalizedLocations.reduce(
(acc, item) => acc + (Number.isNaN(item.available) ? 0 : item.available),
0,
);
const hasProvidedTotal =
total !== undefined &&
total !== null &&
total !== '' &&
!Number.isNaN(providedTotal);
const normalizedLocations = Array.from(nextLocationsMap.values());
setLocations(normalizedLocations); setLocations(normalizedLocations);
setLocationsTotalAvailable( setLocationsTotalAvailable(
hasProvidedTotal ? providedTotal : fallbackTotal, normalizedLocations.reduce(
(acc, item) => acc + (item.available || 0),
0,
),
); );
return normalizedLocations; return normalizedLocations;
} else { } else {
...@@ -132,34 +164,37 @@ export const useDeploymentResources = () => { ...@@ -132,34 +164,37 @@ export const useDeploymentResources = () => {
} }
}, []); }, []);
const fetchAvailableReplicas = useCallback(async (hardwareId, gpuCount = 1) => { const fetchAvailableReplicas = useCallback(
if (!hardwareId) { async (hardwareId, gpuCount = 1) => {
setAvailableReplicas([]); if (!hardwareId) {
return []; setAvailableReplicas([]);
} return [];
}
try { try {
setLoadingReplicas(true); setLoadingReplicas(true);
const response = await API.get( const response = await API.get(
`/api/deployments/available-replicas?hardware_id=${hardwareId}&gpu_count=${gpuCount}` `/api/deployments/available-replicas?hardware_id=${hardwareId}&gpu_count=${gpuCount}`,
); );
if (response.data.success) { if (response.data.success) {
const replicas = response.data.data.replicas || []; const replicas = response.data.data.replicas || [];
setAvailableReplicas(replicas); setAvailableReplicas(replicas);
return replicas; return replicas;
} else { } else {
showError('获取可用资源失败: ' + response.data.message); showError('获取可用资源失败: ' + response.data.message);
setAvailableReplicas([]);
return [];
}
} catch (error) {
console.error('Load available replicas error:', error);
setAvailableReplicas([]); setAvailableReplicas([]);
return []; return [];
} finally {
setLoadingReplicas(false);
} }
} catch (error) { },
console.error('Load available replicas error:', error); [],
setAvailableReplicas([]); );
return [];
} finally {
setLoadingReplicas(false);
}
}, []);
const calculatePrice = useCallback(async (params) => { const calculatePrice = useCallback(async (params) => {
const { const {
...@@ -167,10 +202,16 @@ export const useDeploymentResources = () => { ...@@ -167,10 +202,16 @@ export const useDeploymentResources = () => {
hardwareId, hardwareId,
gpusPerContainer, gpusPerContainer,
durationHours, durationHours,
replicaCount replicaCount,
} = params; } = params;
if (!locationIds?.length || !hardwareId || !gpusPerContainer || !durationHours || !replicaCount) { if (
!locationIds?.length ||
!hardwareId ||
!gpusPerContainer ||
!durationHours ||
!replicaCount
) {
setPriceEstimation(null); setPriceEstimation(null);
return null; return null;
} }
...@@ -185,7 +226,10 @@ export const useDeploymentResources = () => { ...@@ -185,7 +226,10 @@ export const useDeploymentResources = () => {
replica_count: replicaCount, replica_count: replicaCount,
}; };
const response = await API.post('/api/deployments/price-estimation', requestData); const response = await API.post(
'/api/deployments/price-estimation',
requestData,
);
if (response.data.success) { if (response.data.success) {
const estimation = response.data.data; const estimation = response.data.data;
setPriceEstimation(estimation); setPriceEstimation(estimation);
...@@ -208,7 +252,9 @@ export const useDeploymentResources = () => { ...@@ -208,7 +252,9 @@ export const useDeploymentResources = () => {
if (!name?.trim()) return false; if (!name?.trim()) return false;
try { try {
const response = await API.get(`/api/deployments/check-name?name=${encodeURIComponent(name.trim())}`); const response = await API.get(
`/api/deployments/check-name?name=${encodeURIComponent(name.trim())}`,
);
if (response.data.success) { if (response.data.success) {
return response.data.data.available; return response.data.data.available;
} else { } else {
......
...@@ -25,9 +25,9 @@ export const useEnhancedDeploymentActions = (t) => { ...@@ -25,9 +25,9 @@ export const useEnhancedDeploymentActions = (t) => {
// Set loading state for specific operation // Set loading state for specific operation
const setOperationLoading = (operation, deploymentId, isLoading) => { const setOperationLoading = (operation, deploymentId, isLoading) => {
setLoading(prev => ({ setLoading((prev) => ({
...prev, ...prev,
[`${operation}_${deploymentId}`]: isLoading [`${operation}_${deploymentId}`]: isLoading,
})); }));
}; };
...@@ -38,20 +38,26 @@ export const useEnhancedDeploymentActions = (t) => { ...@@ -38,20 +38,26 @@ export const useEnhancedDeploymentActions = (t) => {
// Extend deployment duration // Extend deployment duration
const extendDeployment = async (deploymentId, durationHours) => { const extendDeployment = async (deploymentId, durationHours) => {
const operationKey = `extend_${deploymentId}`;
try { try {
setOperationLoading('extend', deploymentId, true); setOperationLoading('extend', deploymentId, true);
const response = await API.post(`/api/deployments/${deploymentId}/extend`, { const response = await API.post(
duration_hours: durationHours `/api/deployments/${deploymentId}/extend`,
}); {
duration_hours: durationHours,
},
);
if (response.data.success) { if (response.data.success) {
showSuccess(t('容器时长延长成功')); showSuccess(t('容器时长延长成功'));
return response.data.data; return response.data.data;
} }
} catch (error) { } catch (error) {
showError(t('延长时长失败') + ': ' + (error.response?.data?.message || error.message)); showError(
t('延长时长失败') +
': ' +
(error.response?.data?.message || error.message),
);
throw error; throw error;
} finally { } finally {
setOperationLoading('extend', deploymentId, false); setOperationLoading('extend', deploymentId, false);
...@@ -62,14 +68,18 @@ export const useEnhancedDeploymentActions = (t) => { ...@@ -62,14 +68,18 @@ export const useEnhancedDeploymentActions = (t) => {
const getDeploymentDetails = async (deploymentId) => { const getDeploymentDetails = async (deploymentId) => {
try { try {
setOperationLoading('details', deploymentId, true); setOperationLoading('details', deploymentId, true);
const response = await API.get(`/api/deployments/${deploymentId}`); const response = await API.get(`/api/deployments/${deploymentId}`);
if (response.data.success) { if (response.data.success) {
return response.data.data; return response.data.data;
} }
} catch (error) { } catch (error) {
showError(t('获取详情失败') + ': ' + (error.response?.data?.message || error.message)); showError(
t('获取详情失败') +
': ' +
(error.response?.data?.message || error.message),
);
throw error; throw error;
} finally { } finally {
setOperationLoading('details', deploymentId, false); setOperationLoading('details', deploymentId, false);
...@@ -80,24 +90,31 @@ export const useEnhancedDeploymentActions = (t) => { ...@@ -80,24 +90,31 @@ export const useEnhancedDeploymentActions = (t) => {
const getDeploymentLogs = async (deploymentId, options = {}) => { const getDeploymentLogs = async (deploymentId, options = {}) => {
try { try {
setOperationLoading('logs', deploymentId, true); setOperationLoading('logs', deploymentId, true);
const params = new URLSearchParams(); const params = new URLSearchParams();
if (options.containerId) params.append('container_id', options.containerId); if (options.containerId)
params.append('container_id', options.containerId);
if (options.level) params.append('level', options.level); if (options.level) params.append('level', options.level);
if (options.limit) params.append('limit', options.limit.toString()); if (options.limit) params.append('limit', options.limit.toString());
if (options.cursor) params.append('cursor', options.cursor); if (options.cursor) params.append('cursor', options.cursor);
if (options.follow) params.append('follow', 'true'); if (options.follow) params.append('follow', 'true');
if (options.startTime) params.append('start_time', options.startTime); if (options.startTime) params.append('start_time', options.startTime);
if (options.endTime) params.append('end_time', options.endTime); if (options.endTime) params.append('end_time', options.endTime);
const response = await API.get(`/api/deployments/${deploymentId}/logs?${params}`); const response = await API.get(
`/api/deployments/${deploymentId}/logs?${params}`,
);
if (response.data.success) { if (response.data.success) {
return response.data.data; return response.data.data;
} }
} catch (error) { } catch (error) {
showError(t('获取日志失败') + ': ' + (error.response?.data?.message || error.message)); showError(
t('获取日志失败') +
': ' +
(error.response?.data?.message || error.message),
);
throw error; throw error;
} finally { } finally {
setOperationLoading('logs', deploymentId, false); setOperationLoading('logs', deploymentId, false);
...@@ -108,15 +125,22 @@ export const useEnhancedDeploymentActions = (t) => { ...@@ -108,15 +125,22 @@ export const useEnhancedDeploymentActions = (t) => {
const updateDeploymentConfig = async (deploymentId, config) => { const updateDeploymentConfig = async (deploymentId, config) => {
try { try {
setOperationLoading('config', deploymentId, true); setOperationLoading('config', deploymentId, true);
const response = await API.put(`/api/deployments/${deploymentId}`, config); const response = await API.put(
`/api/deployments/${deploymentId}`,
config,
);
if (response.data.success) { if (response.data.success) {
showSuccess(t('容器配置更新成功')); showSuccess(t('容器配置更新成功'));
return response.data.data; return response.data.data;
} }
} catch (error) { } catch (error) {
showError(t('更新配置失败') + ': ' + (error.response?.data?.message || error.message)); showError(
t('更新配置失败') +
': ' +
(error.response?.data?.message || error.message),
);
throw error; throw error;
} finally { } finally {
setOperationLoading('config', deploymentId, false); setOperationLoading('config', deploymentId, false);
...@@ -127,15 +151,19 @@ export const useEnhancedDeploymentActions = (t) => { ...@@ -127,15 +151,19 @@ export const useEnhancedDeploymentActions = (t) => {
const deleteDeployment = async (deploymentId) => { const deleteDeployment = async (deploymentId) => {
try { try {
setOperationLoading('delete', deploymentId, true); setOperationLoading('delete', deploymentId, true);
const response = await API.delete(`/api/deployments/${deploymentId}`); const response = await API.delete(`/api/deployments/${deploymentId}`);
if (response.data.success) { if (response.data.success) {
showSuccess(t('容器销毁请求已提交')); showSuccess(t('容器销毁请求已提交'));
return response.data.data; return response.data.data;
} }
} catch (error) { } catch (error) {
showError(t('销毁容器失败') + ': ' + (error.response?.data?.message || error.message)); showError(
t('销毁容器失败') +
': ' +
(error.response?.data?.message || error.message),
);
throw error; throw error;
} finally { } finally {
setOperationLoading('delete', deploymentId, false); setOperationLoading('delete', deploymentId, false);
...@@ -146,17 +174,21 @@ export const useEnhancedDeploymentActions = (t) => { ...@@ -146,17 +174,21 @@ export const useEnhancedDeploymentActions = (t) => {
const updateDeploymentName = async (deploymentId, newName) => { const updateDeploymentName = async (deploymentId, newName) => {
try { try {
setOperationLoading('rename', deploymentId, true); setOperationLoading('rename', deploymentId, true);
const response = await API.put(`/api/deployments/${deploymentId}/name`, { const response = await API.put(`/api/deployments/${deploymentId}/name`, {
name: newName name: newName,
}); });
if (response.data.success) { if (response.data.success) {
showSuccess(t('容器名称更新成功')); showSuccess(t('容器名称更新成功'));
return response.data.data; return response.data.data;
} }
} catch (error) { } catch (error) {
showError(t('更新名称失败') + ': ' + (error.response?.data?.message || error.message)); showError(
t('更新名称失败') +
': ' +
(error.response?.data?.message || error.message),
);
throw error; throw error;
} finally { } finally {
setOperationLoading('rename', deploymentId, false); setOperationLoading('rename', deploymentId, false);
...@@ -167,21 +199,23 @@ export const useEnhancedDeploymentActions = (t) => { ...@@ -167,21 +199,23 @@ export const useEnhancedDeploymentActions = (t) => {
const batchDelete = async (deploymentIds) => { const batchDelete = async (deploymentIds) => {
try { try {
setOperationLoading('batch_delete', 'all', true); setOperationLoading('batch_delete', 'all', true);
const results = await Promise.allSettled( const results = await Promise.allSettled(
deploymentIds.map(id => deleteDeployment(id)) deploymentIds.map((id) => deleteDeployment(id)),
); );
const successful = results.filter(r => r.status === 'fulfilled').length; const successful = results.filter((r) => r.status === 'fulfilled').length;
const failed = results.filter(r => r.status === 'rejected').length; const failed = results.filter((r) => r.status === 'rejected').length;
if (successful > 0) { if (successful > 0) {
showSuccess(t('批量操作完成: {{success}}个成功, {{failed}}个失败', { showSuccess(
success: successful, t('批量操作完成: {{success}}个成功, {{failed}}个失败', {
failed: failed success: successful,
})); failed: failed,
}),
);
} }
return { successful, failed }; return { successful, failed };
} catch (error) { } catch (error) {
showError(t('批量操作失败') + ': ' + error.message); showError(t('批量操作失败') + ': ' + error.message);
...@@ -195,17 +229,20 @@ export const useEnhancedDeploymentActions = (t) => { ...@@ -195,17 +229,20 @@ export const useEnhancedDeploymentActions = (t) => {
const exportLogs = async (deploymentId, options = {}) => { const exportLogs = async (deploymentId, options = {}) => {
try { try {
setOperationLoading('export_logs', deploymentId, true); setOperationLoading('export_logs', deploymentId, true);
const logs = await getDeploymentLogs(deploymentId, { const logs = await getDeploymentLogs(deploymentId, {
...options, ...options,
limit: 10000 // Get more logs for export limit: 10000, // Get more logs for export
}); });
if (logs && logs.logs) { if (logs && logs.logs) {
const logText = logs.logs.map(log => const logText = logs.logs
`[${new Date(log.timestamp).toISOString()}] [${log.level}] ${log.source ? `[${log.source}] ` : ''}${log.message}` .map(
).join('\n'); (log) =>
`[${new Date(log.timestamp).toISOString()}] [${log.level}] ${log.source ? `[${log.source}] ` : ''}${log.message}`,
)
.join('\n');
const blob = new Blob([logText], { type: 'text/plain' }); const blob = new Blob([logText], { type: 'text/plain' });
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
const a = document.createElement('a'); const a = document.createElement('a');
...@@ -215,7 +252,7 @@ export const useEnhancedDeploymentActions = (t) => { ...@@ -215,7 +252,7 @@ export const useEnhancedDeploymentActions = (t) => {
a.click(); a.click();
document.body.removeChild(a); document.body.removeChild(a);
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
showSuccess(t('日志导出成功')); showSuccess(t('日志导出成功'));
} }
} catch (error) { } catch (error) {
...@@ -236,14 +273,14 @@ export const useEnhancedDeploymentActions = (t) => { ...@@ -236,14 +273,14 @@ export const useEnhancedDeploymentActions = (t) => {
updateDeploymentName, updateDeploymentName,
batchDelete, batchDelete,
exportLogs, exportLogs,
// Loading states // Loading states
isOperationLoading, isOperationLoading,
loading, loading,
// Utility // Utility
setOperationLoading setOperationLoading,
}; };
}; };
export default useEnhancedDeploymentActions; export default useEnhancedDeploymentActions;
\ No newline at end of file
...@@ -18,13 +18,12 @@ For commercial licensing, please contact support@quantumnous.com ...@@ -18,13 +18,12 @@ For commercial licensing, please contact support@quantumnous.com
*/ */
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import { API, toBoolean } from '../../helpers'; import { API } from '../../helpers';
export const useModelDeploymentSettings = () => { export const useModelDeploymentSettings = () => {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [settings, setSettings] = useState({ const [settings, setSettings] = useState({
'model_deployment.ionet.enabled': false, 'model_deployment.ionet.enabled': false,
'model_deployment.ionet.api_key': '',
}); });
const [connectionState, setConnectionState] = useState({ const [connectionState, setConnectionState] = useState({
loading: false, loading: false,
...@@ -35,24 +34,13 @@ export const useModelDeploymentSettings = () => { ...@@ -35,24 +34,13 @@ export const useModelDeploymentSettings = () => {
const getSettings = async () => { const getSettings = async () => {
try { try {
setLoading(true); setLoading(true);
const res = await API.get('/api/option/'); const res = await API.get('/api/deployments/settings');
const { success, data } = res.data; const { success, data } = res.data;
if (success) { if (success) {
const newSettings = { setSettings({
'model_deployment.ionet.enabled': false, 'model_deployment.ionet.enabled': data?.enabled === true,
'model_deployment.ionet.api_key': '',
};
data.forEach((item) => {
if (item.key.endsWith('enabled')) {
newSettings[item.key] = toBoolean(item.value);
} else if (newSettings.hasOwnProperty(item.key)) {
newSettings[item.key] = item.value || '';
}
}); });
setSettings(newSettings);
} }
} catch (error) { } catch (error) {
console.error('Failed to get model deployment settings:', error); console.error('Failed to get model deployment settings:', error);
...@@ -65,10 +53,7 @@ export const useModelDeploymentSettings = () => { ...@@ -65,10 +53,7 @@ export const useModelDeploymentSettings = () => {
getSettings(); getSettings();
}, []); }, []);
const apiKey = settings['model_deployment.ionet.api_key']; const isIoNetEnabled = settings['model_deployment.ionet.enabled'];
const isIoNetEnabled = settings['model_deployment.ionet.enabled'] &&
apiKey &&
apiKey.trim() !== '';
const buildConnectionError = (rawMessage, fallbackMessage = 'Connection failed') => { const buildConnectionError = (rawMessage, fallbackMessage = 'Connection failed') => {
const message = (rawMessage || fallbackMessage).trim(); const message = (rawMessage || fallbackMessage).trim();
...@@ -85,18 +70,12 @@ export const useModelDeploymentSettings = () => { ...@@ -85,18 +70,12 @@ export const useModelDeploymentSettings = () => {
return { type: 'unknown', message }; return { type: 'unknown', message };
}; };
const testConnection = useCallback(async (apiKey) => { const testConnection = useCallback(async () => {
const key = (apiKey || '').trim();
if (key === '') {
setConnectionState({ loading: false, ok: null, error: null });
return;
}
setConnectionState({ loading: true, ok: null, error: null }); setConnectionState({ loading: true, ok: null, error: null });
try { try {
const response = await API.post( const response = await API.post(
'/api/deployments/test-connection', '/api/deployments/settings/test-connection',
{ api_key: key }, {},
{ skipErrorHandler: true }, { skipErrorHandler: true },
); );
...@@ -123,16 +102,15 @@ export const useModelDeploymentSettings = () => { ...@@ -123,16 +102,15 @@ export const useModelDeploymentSettings = () => {
useEffect(() => { useEffect(() => {
if (!loading && isIoNetEnabled) { if (!loading && isIoNetEnabled) {
testConnection(apiKey); testConnection();
return; return;
} }
setConnectionState({ loading: false, ok: null, error: null }); setConnectionState({ loading: false, ok: null, error: null });
}, [loading, isIoNetEnabled, apiKey, testConnection]); }, [loading, isIoNetEnabled, testConnection]);
return { return {
loading, loading,
settings, settings,
apiKey,
isIoNetEnabled, isIoNetEnabled,
refresh: getSettings, refresh: getSettings,
connectionLoading: connectionState.loading, connectionLoading: connectionState.loading,
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -29,7 +29,6 @@ const ModelDeploymentPage = () => { ...@@ -29,7 +29,6 @@ const ModelDeploymentPage = () => {
connectionLoading, connectionLoading,
connectionOk, connectionOk,
connectionError, connectionError,
apiKey,
testConnection, testConnection,
} = useModelDeploymentSettings(); } = useModelDeploymentSettings();
...@@ -40,7 +39,7 @@ const ModelDeploymentPage = () => { ...@@ -40,7 +39,7 @@ const ModelDeploymentPage = () => {
connectionLoading={connectionLoading} connectionLoading={connectionLoading}
connectionOk={connectionOk} connectionOk={connectionOk}
connectionError={connectionError} connectionError={connectionError}
onRetry={() => testConnection(apiKey)} onRetry={() => testConnection()}
> >
<div className='mt-[60px] px-2'> <div className='mt-[60px] px-2'>
<DeploymentsTable /> <DeploymentsTable />
......
...@@ -48,10 +48,6 @@ export default function SettingModelDeployment(props) { ...@@ -48,10 +48,6 @@ export default function SettingModelDeployment(props) {
const testApiKey = async () => { const testApiKey = async () => {
const apiKey = inputs['model_deployment.ionet.api_key']; const apiKey = inputs['model_deployment.ionet.api_key'];
if (!apiKey || apiKey.trim() === '') {
showError(t('请先填写 API Key'));
return;
}
const getLocalizedMessage = (message) => { const getLocalizedMessage = (message) => {
switch (message) { switch (message) {
...@@ -69,10 +65,8 @@ export default function SettingModelDeployment(props) { ...@@ -69,10 +65,8 @@ export default function SettingModelDeployment(props) {
setTesting(true); setTesting(true);
try { try {
const response = await API.post( const response = await API.post(
'/api/deployments/test-connection', '/api/deployments/settings/test-connection',
{ apiKey && apiKey.trim() !== '' ? { api_key: apiKey.trim() } : {},
api_key: apiKey.trim(),
},
{ {
skipErrorHandler: true, skipErrorHandler: true,
}, },
...@@ -108,12 +102,6 @@ export default function SettingModelDeployment(props) { ...@@ -108,12 +102,6 @@ export default function SettingModelDeployment(props) {
}; };
function onSubmit() { function onSubmit() {
// 前置校验:如果启用了 io.net 但没有填写 API Key
if (inputs['model_deployment.ionet.enabled'] &&
(!inputs['model_deployment.ionet.api_key'] || inputs['model_deployment.ionet.api_key'].trim() === '')) {
return showError(t('启用 io.net 部署时必须填写 API Key'));
}
const updateArray = compareObjects(inputs, inputsRow); const updateArray = compareObjects(inputs, inputsRow);
if (!updateArray.length) return showWarning(t('你似乎并没有修改什么')); if (!updateArray.length) return showWarning(t('你似乎并没有修改什么'));
...@@ -229,7 +217,7 @@ export default function SettingModelDeployment(props) { ...@@ -229,7 +217,7 @@ export default function SettingModelDeployment(props) {
<Form.Input <Form.Input
label={t('API Key')} label={t('API Key')}
field={'model_deployment.ionet.api_key'} field={'model_deployment.ionet.api_key'}
placeholder={t('请输入 io.net API Key')} placeholder={t('请输入 io.net API Key(敏感信息不显示)')}
onChange={(value) => onChange={(value) =>
setInputs({ setInputs({
...inputs, ...inputs,
...@@ -248,9 +236,7 @@ export default function SettingModelDeployment(props) { ...@@ -248,9 +236,7 @@ export default function SettingModelDeployment(props) {
onClick={testApiKey} onClick={testApiKey}
loading={testing} loading={testing}
disabled={ disabled={
!inputs['model_deployment.ionet.enabled'] || !inputs['model_deployment.ionet.enabled']
!inputs['model_deployment.ionet.api_key'] ||
inputs['model_deployment.ionet.api_key'].trim() === ''
} }
style={{ style={{
height: '32px', height: '32px',
......
...@@ -32,7 +32,7 @@ import { API, showSuccess, showError } from '../../../helpers'; ...@@ -32,7 +32,7 @@ import { API, showSuccess, showError } from '../../../helpers';
import { StatusContext } from '../../../context/Status'; import { StatusContext } from '../../../context/Status';
import { UserContext } from '../../../context/User'; import { UserContext } from '../../../context/User';
import { useUserPermissions } from '../../../hooks/common/useUserPermissions'; import { useUserPermissions } from '../../../hooks/common/useUserPermissions';
import { useSidebar } from '../../../hooks/common/useSidebar'; import { mergeAdminConfig, useSidebar } from '../../../hooks/common/useSidebar';
import { Settings } from 'lucide-react'; import { Settings } from 'lucide-react';
const { Text } = Typography; const { Text } = Typography;
...@@ -198,9 +198,25 @@ export default function SettingsSidebarModulesUser() { ...@@ -198,9 +198,25 @@ export default function SettingsSidebarModulesUser() {
try { try {
// 获取管理员全局配置 // 获取管理员全局配置
if (statusState?.status?.SidebarModulesAdmin) { if (statusState?.status?.SidebarModulesAdmin) {
const adminConf = JSON.parse(statusState.status.SidebarModulesAdmin); try {
setAdminConfig(adminConf); const adminConf = JSON.parse(
console.log('加载管理员边栏配置:', adminConf); statusState.status.SidebarModulesAdmin,
);
const mergedAdminConf = mergeAdminConfig(adminConf);
setAdminConfig(mergedAdminConf);
console.log('加载管理员边栏配置:', mergedAdminConf);
} catch (error) {
const mergedAdminConf = mergeAdminConfig(null);
setAdminConfig(mergedAdminConf);
console.log(
'加载管理员边栏配置失败,使用默认配置:',
mergedAdminConf,
);
}
} else {
const mergedAdminConf = mergeAdminConfig(null);
setAdminConfig(mergedAdminConf);
console.log('管理员边栏配置缺失,使用默认配置:', mergedAdminConf);
} }
// 获取用户个人配置 // 获取用户个人配置
...@@ -324,6 +340,11 @@ export default function SettingsSidebarModulesUser() { ...@@ -324,6 +340,11 @@ export default function SettingsSidebarModulesUser() {
{ key: 'channel', title: t('渠道管理'), description: t('API渠道配置') }, { key: 'channel', title: t('渠道管理'), description: t('API渠道配置') },
{ key: 'models', title: t('模型管理'), description: t('AI模型配置') }, { key: 'models', title: t('模型管理'), description: t('AI模型配置') },
{ {
key: 'deployment',
title: t('模型部署'),
description: t('模型部署管理'),
},
{
key: 'redemption', key: 'redemption',
title: t('兑换码管理'), title: t('兑换码管理'),
description: t('兑换码生成管理'), description: t('兑换码生成管理'),
...@@ -389,7 +410,7 @@ export default function SettingsSidebarModulesUser() { ...@@ -389,7 +410,7 @@ export default function SettingsSidebarModulesUser() {
</Text> </Text>
</div> </div>
<Switch <Switch
checked={sidebarModulesUser[section.key]?.enabled} checked={sidebarModulesUser[section.key]?.enabled !== false}
onChange={handleSectionChange(section.key)} onChange={handleSectionChange(section.key)}
size='default' size='default'
/> />
...@@ -401,7 +422,9 @@ export default function SettingsSidebarModulesUser() { ...@@ -401,7 +422,9 @@ export default function SettingsSidebarModulesUser() {
<Col key={module.key} xs={24} sm={12} md={8} lg={6} xl={6}> <Col key={module.key} xs={24} sm={12} md={8} lg={6} xl={6}>
<Card <Card
className={`!rounded-xl border border-gray-200 hover:border-blue-300 transition-all duration-200 ${ className={`!rounded-xl border border-gray-200 hover:border-blue-300 transition-all duration-200 ${
sidebarModulesUser[section.key]?.enabled ? '' : 'opacity-50' sidebarModulesUser[section.key]?.enabled !== false
? ''
: 'opacity-50'
}`} }`}
bodyStyle={{ padding: '16px' }} bodyStyle={{ padding: '16px' }}
hoverable hoverable
...@@ -417,10 +440,15 @@ export default function SettingsSidebarModulesUser() { ...@@ -417,10 +440,15 @@ export default function SettingsSidebarModulesUser() {
</div> </div>
<div className='ml-4'> <div className='ml-4'>
<Switch <Switch
checked={sidebarModulesUser[section.key]?.[module.key]} checked={
sidebarModulesUser[section.key]?.[module.key] !==
false
}
onChange={handleModuleChange(section.key, module.key)} onChange={handleModuleChange(section.key, module.key)}
size='default' size='default'
disabled={!sidebarModulesUser[section.key]?.enabled} disabled={
sidebarModulesUser[section.key]?.enabled === false
}
/> />
</div> </div>
</div> </div>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment