feat: add reload button. (#471)

This commit is contained in:
cheestard
2025-12-03 18:55:48 +08:00
committed by GitHub
parent 61b748151f
commit 54e877cbd8
10 changed files with 109 additions and 13 deletions

View File

@@ -15,14 +15,16 @@ interface ServerCardProps {
onEdit: (server: Server) => void;
onToggle?: (server: Server, enabled: boolean) => Promise<boolean>;
onRefresh?: () => void;
onReload?: (server: Server) => Promise<boolean>;
}
const ServerCard = ({ server, onRemove, onEdit, onToggle, onRefresh }: ServerCardProps) => {
const ServerCard = ({ server, onRemove, onEdit, onToggle, onRefresh, onReload }: ServerCardProps) => {
const { t } = useTranslation();
const { showToast } = useToast();
const [isExpanded, setIsExpanded] = useState(false);
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
const [isToggling, setIsToggling] = useState(false);
const [isReloading, setIsReloading] = useState(false);
const [showErrorPopover, setShowErrorPopover] = useState(false);
const [copied, setCopied] = useState(false);
const errorPopoverRef = useRef<HTMLDivElement>(null);
@@ -64,6 +66,26 @@ const ServerCard = ({ server, onRemove, onEdit, onToggle, onRefresh }: ServerCar
}
};
const handleReload = async (e: React.MouseEvent) => {
e.stopPropagation();
if (isReloading || !onReload) return;
setIsReloading(true);
try {
const success = await onReload(server);
if (success) {
showToast(t('server.reloadSuccess') || 'Server reloaded successfully', 'success');
} else {
showToast(
t('server.reloadError', { serverName: server.name }) || 'Failed to reload server',
'error',
);
}
} finally {
setIsReloading(false);
}
};
const handleErrorIconClick = (e: React.MouseEvent) => {
e.stopPropagation();
setShowErrorPopover(!showErrorPopover);
@@ -330,7 +352,7 @@ const ServerCard = ({ server, onRemove, onEdit, onToggle, onRefresh }: ServerCar
? 'bg-green-100 text-green-800 hover:bg-green-200 btn-secondary'
: 'bg-gray-100 text-gray-800 hover:bg-gray-200 btn-primary'
}`}
disabled={isToggling}
disabled={isToggling || isReloading}
>
{isToggling
? t('common.processing')
@@ -339,6 +361,15 @@ const ServerCard = ({ server, onRemove, onEdit, onToggle, onRefresh }: ServerCar
: t('server.enable')}
</button>
</div>
{server.enabled !== false && onReload && (
<button
onClick={handleReload}
className="px-3 py-1 bg-purple-100 text-purple-800 rounded hover:bg-purple-200 text-sm btn-secondary disabled:opacity-70 disabled:cursor-not-allowed"
disabled={isReloading || isToggling}
>
{isReloading ? t('common.processing') : t('server.reload')}
</button>
)}
<button
onClick={handleRemove}
className="px-3 py-1 bg-red-100 text-red-800 rounded hover:bg-red-200 text-sm btn-danger"

View File

@@ -30,6 +30,7 @@ interface ServerContextType {
handleServerEdit: (server: Server) => Promise<any>;
handleServerRemove: (serverName: string) => Promise<boolean>;
handleServerToggle: (server: Server, enabled: boolean) => Promise<boolean>;
handleServerReload: (server: Server) => Promise<boolean>;
}
// Create Context
@@ -358,6 +359,30 @@ export const ServerProvider: React.FC<{ children: React.ReactNode }> = ({ childr
[t],
);
const handleServerReload = useCallback(
async (server: Server) => {
try {
const encodedServerName = encodeURIComponent(server.name);
const result = await apiPost(`/servers/${encodedServerName}/reload`, {});
if (!result || !result.success) {
console.error('Failed to reload server:', result);
setError(t('server.reloadError', { serverName: server.name }));
return false;
}
// Refresh server list after successful reload
triggerRefresh();
return true;
} catch (err) {
console.error('Error reloading server:', err);
setError(err instanceof Error ? err.message : String(err));
return false;
}
},
[t, triggerRefresh],
);
const value: ServerContextType = {
servers,
error,
@@ -370,6 +395,7 @@ export const ServerProvider: React.FC<{ children: React.ReactNode }> = ({ childr
handleServerEdit,
handleServerRemove,
handleServerToggle,
handleServerReload,
};
return <ServerContext.Provider value={value}>{children}</ServerContext.Provider>;

View File

@@ -21,6 +21,7 @@ const ServersPage: React.FC = () => {
handleServerEdit,
handleServerRemove,
handleServerToggle,
handleServerReload,
triggerRefresh
} = useServerData({ refreshOnMount: true });
const [editingServer, setEditingServer] = useState<Server | null>(null);
@@ -159,6 +160,7 @@ const ServersPage: React.FC = () => {
onEdit={handleEditClick}
onToggle={handleServerToggle}
onRefresh={triggerRefresh}
onReload={handleServerReload}
/>
))}
</div>
@@ -189,4 +191,4 @@ const ServersPage: React.FC = () => {
);
};
export default ServersPage;
export default ServersPage;

View File

@@ -116,6 +116,9 @@
"enabled": "Enabled",
"enable": "Enable",
"disable": "Disable",
"reload": "Reload",
"reloadSuccess": "Server reloaded successfully",
"reloadError": "Failed to reload server {{serverName}}",
"requestOptions": "Connection Configuration",
"timeout": "Request Timeout",
"timeoutDescription": "Timeout for requests to the MCP server (ms)",
@@ -725,6 +728,7 @@
"failedToRemoveServer": "Server not found or failed to remove",
"internalServerError": "Internal server error",
"failedToGetServers": "Failed to get servers information",
"failedToReloadServer": "Failed to reload server",
"failedToGetServerSettings": "Failed to get server settings",
"failedToGetServerConfig": "Failed to get server configuration",
"failedToSaveSettings": "Failed to save settings",

View File

@@ -116,6 +116,9 @@
"enabled": "Activé",
"enable": "Activer",
"disable": "Désactiver",
"reload": "Recharger",
"reloadSuccess": "Serveur rechargé avec succès",
"reloadError": "Échec du rechargement du serveur {{serverName}}",
"requestOptions": "Configuration de la connexion",
"timeout": "Délai d'attente de la requête",
"timeoutDescription": "Délai d'attente pour les requêtes vers le serveur MCP (ms)",
@@ -208,6 +211,7 @@
"serverAdd": "Échec de l'ajout du serveur. Veuillez vérifier l'état du serveur",
"serverUpdate": "Échec de la modification du serveur {{serverName}}. Veuillez vérifier l'état du serveur",
"serverFetch": "Échec de la récupération des données du serveur. Veuillez réessayer plus tard",
"failedToReloadServer": "Échec du rechargement du serveur",
"initialStartup": "Le serveur est peut-être en cours de démarrage. Veuillez patienter un instant car ce processus peut prendre du temps au premier lancement...",
"serverInstall": "Échec de l'installation du serveur",
"failedToFetchSettings": "Échec de la récupération des paramètres",

View File

@@ -116,6 +116,9 @@
"enabled": "Etkin",
"enable": "Etkinleştir",
"disable": "Devre Dışı Bırak",
"reload": "Yeniden Yükle",
"reloadSuccess": "Sunucu başarıyla yeniden yüklendi",
"reloadError": "Sunucu {{serverName}} yeniden yüklenemedi",
"requestOptions": "Bağlantı Yapılandırması",
"timeout": "İstek Zaman Aşımı",
"timeoutDescription": "MCP sunucusuna yapılan istekler için zaman aşımı (ms)",
@@ -208,6 +211,7 @@
"serverAdd": "Sunucu eklenemedi. Lütfen sunucu durumunu kontrol edin",
"serverUpdate": "{{serverName}} sunucusu düzenlenemedi. Lütfen sunucu durumunu kontrol edin",
"serverFetch": "Sunucu verileri alınamadı. Lütfen daha sonra tekrar deneyin",
"failedToReloadServer": "Sunucu yeniden yüklenemedi",
"initialStartup": "Sunucu başlatılıyor olabilir. İlk başlatmada bu işlem biraz zaman alabileceğinden lütfen bekleyin...",
"serverInstall": "Sunucu yüklenemedi",
"failedToFetchSettings": "Ayarlar getirilemedi",

View File

@@ -116,6 +116,9 @@
"enabled": "已启用",
"enable": "启用",
"disable": "禁用",
"reload": "重载",
"reloadSuccess": "服务器重载成功",
"reloadError": "重载服务器 {{serverName}} 失败",
"requestOptions": "连接配置",
"timeout": "请求超时",
"timeoutDescription": "请求超时时间(毫秒)",
@@ -208,6 +211,7 @@
"serverAdd": "添加服务器失败,请检查服务器状态",
"serverUpdate": "编辑服务器 {{serverName}} 失败,请检查服务器状态",
"serverFetch": "获取服务器数据失败,请稍后重试",
"failedToReloadServer": "重载服务器失败",
"initialStartup": "服务器可能正在启动中。首次启动可能需要一些时间,请耐心等候...",
"serverInstall": "安装服务器失败",
"failedToFetchSettings": "获取设置失败",

View File

@@ -8,6 +8,7 @@ import {
notifyToolChanged,
syncToolEmbedding,
toggleServerStatus,
reconnectServer,
} from '../services/mcpService.js';
import { loadSettings } from '../config/index.js';
import { syncAllServerToolsEmbeddings } from '../services/vectorSearchService.js';
@@ -415,6 +416,32 @@ export const toggleServer = async (req: Request, res: Response): Promise<void> =
}
};
export const reloadServer = async (req: Request, res: Response): Promise<void> => {
try {
const { name } = req.params;
if (!name) {
res.status(400).json({
success: false,
message: 'Server name is required',
});
return;
}
await reconnectServer(name);
res.json({
success: true,
message: `Server ${name} reloaded successfully`,
});
} catch (error) {
console.error('Failed to reload server:', error);
res.status(500).json({
success: false,
message: 'Failed to reload server',
});
}
};
// Toggle tool status for a specific server
export const toggleTool = async (req: Request, res: Response): Promise<void> => {
try {

View File

@@ -9,6 +9,7 @@ import {
updateServer,
deleteServer,
toggleServer,
reloadServer,
toggleTool,
updateToolDescription,
togglePrompt,
@@ -136,6 +137,7 @@ export const initRoutes = (app: express.Application): void => {
router.put('/servers/:name', updateServer);
router.delete('/servers/:name', deleteServer);
router.post('/servers/:name/toggle', toggleServer);
router.post('/servers/:name/reload', reloadServer);
router.post('/servers/:serverName/tools/:toolName/toggle', toggleTool);
router.put('/servers/:serverName/tools/:toolName/description', updateToolDescription);
router.post('/servers/:serverName/prompts/:promptName/toggle', togglePrompt);

View File

@@ -277,11 +277,7 @@ const callToolWithReconnect = async (
version: '1.0.0',
},
{
capabilities: {
prompts: {},
resources: {},
tools: {},
},
capabilities: {},
},
);
@@ -463,11 +459,7 @@ export const initializeClientsFromSettings = async (
version: '1.0.0',
},
{
capabilities: {
prompts: {},
resources: {},
tools: {},
},
capabilities: {},
},
);