서버 관리
This commit is contained in:
+32
-26
@@ -1,26 +1,26 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, {useEffect, useState} from 'react';
|
||||||
import { APIProvider, useAPI } from './providers/APIProvider';
|
import {APIProvider, useAPI} from './providers/APIProvider';
|
||||||
import { LoadTestProvider } from './providers/LoadTestProvider';
|
import {LoadTestProvider} from './providers/LoadTestProvider';
|
||||||
import DialogManager from './components/shared/DialogManager';
|
import DialogManager from '@/components/shared/DialogManager';
|
||||||
import { monacoConfig } from '@/config/monaco.js';
|
import {monacoConfig} from '@/config/monaco.js';
|
||||||
import LoginDialog from '@/components/account/LoginDialog.jsx';
|
import LoginDialog from '@/components/account/LoginDialog.jsx';
|
||||||
import AppMenu from './components/app-menu/AppMenu';
|
import AppMenu from './components/app-menu/AppMenu';
|
||||||
import { SCREENS } from './config/screenConfig';
|
import {SCREENS} from './config/screenConfig';
|
||||||
|
|
||||||
window.MonacoEnvironment = monacoConfig;
|
window.MonacoEnvironment = monacoConfig;
|
||||||
|
|
||||||
const App = ({ options = { contextPath: '/' } }) => {
|
const App = ({options = {contextPath: '/'}}) => {
|
||||||
const [showLogin, setShowLogin] = useState(false);
|
const [showLogin, setShowLogin] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<APIProvider contextPath={options.contextPath}>
|
<APIProvider contextPath={options.contextPath}>
|
||||||
<AppContent options={options} showLogin={showLogin} setShowLogin={setShowLogin} />
|
<AppContent options={options} showLogin={showLogin} setShowLogin={setShowLogin}/>
|
||||||
</APIProvider>
|
</APIProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const AppContent = ({ options, showLogin, setShowLogin }) => {
|
const AppContent = ({options, showLogin, setShowLogin}) => {
|
||||||
const { isAuthenticated, setIsAuthenticated, handleLogout } = useAPI();
|
const {isAuthenticated, setIsAuthenticated, handleLogout} = useAPI();
|
||||||
const [isInitialized, setIsInitialized] = useState(false);
|
const [isInitialized, setIsInitialized] = useState(false);
|
||||||
const [currentScreen, setCurrentScreen] = useState('api-tester');
|
const [currentScreen, setCurrentScreen] = useState('api-tester');
|
||||||
const [dialogState, setDialogState] = useState({
|
const [dialogState, setDialogState] = useState({
|
||||||
@@ -33,7 +33,8 @@ const AppContent = ({ options, showLogin, setShowLogin }) => {
|
|||||||
showQueueEdit: false,
|
showQueueEdit: false,
|
||||||
showUserEdit: false,
|
showUserEdit: false,
|
||||||
showPasswordChange: false,
|
showPasswordChange: false,
|
||||||
selectedItem: null,
|
showServerEdit: false,
|
||||||
|
selectedItem: null
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleMenuClick = async (action, screen, item = null) => {
|
const handleMenuClick = async (action, screen, item = null) => {
|
||||||
@@ -50,41 +51,45 @@ const AppContent = ({ options, showLogin, setShowLogin }) => {
|
|||||||
// Handle modal actions
|
// Handle modal actions
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case 'request-add':
|
case 'request-add':
|
||||||
setDialogState(prev => ({ ...prev, showAddApi: true }));
|
setDialogState(prev => ({...prev, showAddApi: true}));
|
||||||
break;
|
break;
|
||||||
case 'response-add':
|
case 'response-add':
|
||||||
case 'response-edit':
|
case 'response-edit':
|
||||||
case 'response-clone':
|
case 'response-clone':
|
||||||
setDialogState(prev => ({...prev, showMockRouteEdit: true, selectedItem: item }));
|
setDialogState(prev => ({...prev, showMockRouteEdit: true, selectedItem: item}));
|
||||||
break;
|
break;
|
||||||
case 'queue-add':
|
case 'queue-add':
|
||||||
case 'queue-edit':
|
case 'queue-edit':
|
||||||
setDialogState(prev => ({ ...prev, showQueueEdit: true , selectedItem: item}));
|
setDialogState(prev => ({...prev, showQueueEdit: true, selectedItem: item}));
|
||||||
break;
|
break;
|
||||||
case 'user-add':
|
case 'user-add':
|
||||||
case 'user-edit':
|
case 'user-edit':
|
||||||
setDialogState(prev => ({ ...prev, showUserEdit: true , selectedItem: item}));
|
setDialogState(prev => ({...prev, showUserEdit: true, selectedItem: item}));
|
||||||
break;
|
break;
|
||||||
case 'socket-add':
|
case 'socket-add':
|
||||||
case 'socket-edit':
|
case 'socket-edit':
|
||||||
setDialogState(prev => ({ ...prev, showTCPServerEdit: true , selectedItem: item}));
|
setDialogState(prev => ({...prev, showTCPServerEdit: true, selectedItem: item}));
|
||||||
|
break;
|
||||||
|
case 'server-add':
|
||||||
|
case 'server-edit':
|
||||||
|
setDialogState(prev => ({...prev, showServerEdit: true, selectedItem: item}));
|
||||||
break;
|
break;
|
||||||
case 'collection-add':
|
case 'collection-add':
|
||||||
setDialogState(prev => ({ ...prev, showAddCollection: true }));
|
setDialogState(prev => ({...prev, showAddCollection: true}));
|
||||||
break;
|
break;
|
||||||
case 'scenario-add':
|
case 'scenario-add':
|
||||||
setDialogState(prev => ({ ...prev, showAddScenario: true }));
|
setDialogState(prev => ({...prev, showAddScenario: true}));
|
||||||
break;
|
break;
|
||||||
case 'variables-show':
|
case 'variables-show':
|
||||||
setDialogState(prev => ({ ...prev, showVariableModal: true }));
|
setDialogState(prev => ({...prev, showVariableModal: true}));
|
||||||
break;
|
break;
|
||||||
case 'change-password':
|
case 'change-password':
|
||||||
setDialogState(prev => ({ ...prev, showPasswordChange: true }));
|
setDialogState(prev => ({...prev, showPasswordChange: true}));
|
||||||
break;
|
break;
|
||||||
case 'logout':
|
case 'logout':
|
||||||
const success = await handleLogout();
|
const success = await handleLogout();
|
||||||
if (success) {
|
if (success) {
|
||||||
window.location.href = "/actionLogout.do";
|
window.location.href = `${options.contextPath}/actionLogout.do`;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -117,14 +122,14 @@ const AppContent = ({ options, showLogin, setShowLogin }) => {
|
|||||||
setIsInitialized(true);
|
setIsInitialized(true);
|
||||||
checkAuth();
|
checkAuth();
|
||||||
}
|
}
|
||||||
}, [isInitialized]);
|
}, [isInitialized, checkAuth]);
|
||||||
|
|
||||||
const handleDialogChange = (dialogName, isOpen) => {
|
const handleDialogChange = (dialogName, isOpen) => {
|
||||||
setDialogState(prev => ({ ...prev, [dialogName]: isOpen }));
|
setDialogState(prev => ({...prev, [dialogName]: isOpen}));
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!isAuthenticated) {
|
if (!isAuthenticated) {
|
||||||
return <LoginDialog open={showLogin} onOpenChange={setShowLogin} />;
|
return <LoginDialog open={showLogin} onOpenChange={setShowLogin}/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CurrentScreenComponent = SCREENS[currentScreen] || SCREENS['api-tester'];
|
const CurrentScreenComponent = SCREENS[currentScreen] || SCREENS['api-tester'];
|
||||||
@@ -132,7 +137,7 @@ const AppContent = ({ options, showLogin, setShowLogin }) => {
|
|||||||
return (
|
return (
|
||||||
<LoadTestProvider contextPath={options.contextPath}>
|
<LoadTestProvider contextPath={options.contextPath}>
|
||||||
<div className="flex flex-col h-screen bg-background">
|
<div className="flex flex-col h-screen bg-background">
|
||||||
<AppMenu onMenuClick={handleMenuClick} />
|
<AppMenu onMenuClick={handleMenuClick}/>
|
||||||
<DialogManager
|
<DialogManager
|
||||||
showAddApi={dialogState.showAddApi}
|
showAddApi={dialogState.showAddApi}
|
||||||
showAddCollection={dialogState.showAddCollection}
|
showAddCollection={dialogState.showAddCollection}
|
||||||
@@ -144,10 +149,11 @@ const AppContent = ({ options, showLogin, setShowLogin }) => {
|
|||||||
showUserEdit={dialogState.showUserEdit}
|
showUserEdit={dialogState.showUserEdit}
|
||||||
selectedItem={dialogState.selectedItem}
|
selectedItem={dialogState.selectedItem}
|
||||||
showPasswordChange={dialogState.showPasswordChange}
|
showPasswordChange={dialogState.showPasswordChange}
|
||||||
|
showServerEdit={dialogState.showServerEdit}
|
||||||
onDialogChange={handleDialogChange}
|
onDialogChange={handleDialogChange}
|
||||||
/>
|
/>
|
||||||
<div className="flex-1 mt-14">
|
<div className="flex-1 mt-14">
|
||||||
<CurrentScreenComponent onMenuClick={handleMenuClick} />
|
<CurrentScreenComponent onMenuClick={handleMenuClick}/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</LoadTestProvider>
|
</LoadTestProvider>
|
||||||
|
|||||||
@@ -0,0 +1,295 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle
|
||||||
|
} from '@/components/ui/dialog';
|
||||||
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
import { Label } from '@/components/ui/label';
|
||||||
|
import { Checkbox } from '@/components/ui/checkbox';
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableHeader,
|
||||||
|
TableRow
|
||||||
|
} from '@/components/ui/table';
|
||||||
|
import { useAPI } from '@/providers/APIProvider';
|
||||||
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||||
|
import { Plus, Trash2 } from 'lucide-react';
|
||||||
|
|
||||||
|
const DEFAULT_SERVER = {
|
||||||
|
id: null,
|
||||||
|
name: '',
|
||||||
|
scheme: 'http',
|
||||||
|
hostname: '',
|
||||||
|
port: 80,
|
||||||
|
basePath: '/',
|
||||||
|
enabled: true,
|
||||||
|
serverOptions: []
|
||||||
|
};
|
||||||
|
|
||||||
|
const ServerEditDialog = ({ open, onOpenChange, item }) => {
|
||||||
|
const { createServer, updateServer } = useAPI();
|
||||||
|
const [server, setServer] = useState(DEFAULT_SERVER);
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// if (item) {
|
||||||
|
// // Ensure serverOptions is always an array
|
||||||
|
// setServer({
|
||||||
|
// ...item,
|
||||||
|
// serverOptions: item.serverOptions || []
|
||||||
|
// });
|
||||||
|
// } else {
|
||||||
|
// setServer(DEFAULT_SERVER);
|
||||||
|
// }
|
||||||
|
// }, [item]);
|
||||||
|
useEffect(() => {
|
||||||
|
console.log('Dialog useEffect - item:', item, 'open:', open); // Debug log
|
||||||
|
if (!open) {
|
||||||
|
setServer(DEFAULT_SERVER);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item) {
|
||||||
|
setServer({
|
||||||
|
...item,
|
||||||
|
serverOptions: item.serverOptions || []
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setServer(DEFAULT_SERVER);
|
||||||
|
}
|
||||||
|
}, [item, open]);
|
||||||
|
|
||||||
|
const handleChange = (field, value) => {
|
||||||
|
setServer(prev => ({ ...prev, [field]: value }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const addOption = () => {
|
||||||
|
setServer(prev => ({
|
||||||
|
...prev,
|
||||||
|
serverOptions: [
|
||||||
|
...(prev.serverOptions || []),
|
||||||
|
{
|
||||||
|
optionKey: '',
|
||||||
|
optionName: '',
|
||||||
|
offset: 0,
|
||||||
|
length: 0,
|
||||||
|
defaultValue: ''
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeOption = (index) => {
|
||||||
|
setServer(prev => {
|
||||||
|
const newOptions = [...prev.serverOptions];
|
||||||
|
newOptions.splice(index, 1);
|
||||||
|
return { ...prev, serverOptions: newOptions };
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateOptionField = (index, field, value) => {
|
||||||
|
setServer(prev => {
|
||||||
|
const newOptions = [...prev.serverOptions];
|
||||||
|
newOptions[index] = { ...newOptions[index], [field]: value };
|
||||||
|
return { ...prev, serverOptions: newOptions };
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
try {
|
||||||
|
if (server.id) {
|
||||||
|
await updateServer(server);
|
||||||
|
} else {
|
||||||
|
await createServer(server);
|
||||||
|
}
|
||||||
|
onOpenChange(false);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to save server:', error);
|
||||||
|
alert('Failed to save server');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDialogChange = (open) => {
|
||||||
|
if (!open) {
|
||||||
|
setServer(DEFAULT_SERVER);
|
||||||
|
}
|
||||||
|
onOpenChange(open);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={handleDialogChange}>
|
||||||
|
<DialogContent className="max-w-3xl">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>
|
||||||
|
{server.id == null ? '서버 등록' : '서버 수정'}
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
서버의 정보를 입력하거나 수정할 수 있습니다. 필수 항목을 모두 입력해주세요.
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<ScrollArea className="max-h-[80vh]">
|
||||||
|
<form id="serverForm" onSubmit={handleSubmit} className="space-y-4 p-1">
|
||||||
|
{/* Server Name */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="name" className="must">서버명</Label>
|
||||||
|
<Input
|
||||||
|
id="name"
|
||||||
|
required
|
||||||
|
value={server.name}
|
||||||
|
onChange={(e) => handleChange('name', e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Server Address */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label className="must">서버 주소</Label>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Select
|
||||||
|
value={server.scheme}
|
||||||
|
onValueChange={(value) => handleChange('scheme', value)}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="w-[120px]">
|
||||||
|
<SelectValue placeholder="Scheme" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="http">http</SelectItem>
|
||||||
|
<SelectItem value="https">https</SelectItem>
|
||||||
|
<SelectItem value="tcp">tcp</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<span className="flex items-center">://</span>
|
||||||
|
<Input
|
||||||
|
placeholder="hostname"
|
||||||
|
required
|
||||||
|
value={server.hostname}
|
||||||
|
onChange={(e) => handleChange('hostname', e.target.value)}
|
||||||
|
/>
|
||||||
|
<span className="flex items-center">:</span>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
required
|
||||||
|
className="w-[120px]"
|
||||||
|
value={server.port}
|
||||||
|
onChange={(e) => handleChange('port', parseInt(e.target.value))}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
placeholder="base path"
|
||||||
|
required
|
||||||
|
value={server.basePath}
|
||||||
|
onChange={(e) => handleChange('basePath', e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Enabled Status */}
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Checkbox
|
||||||
|
id="enabled"
|
||||||
|
checked={server.enabled}
|
||||||
|
onCheckedChange={(checked) => handleChange('enabled', checked)}
|
||||||
|
/>
|
||||||
|
<Label htmlFor="enabled">사용</Label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Server Options */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<Label>서버 메시지 옵션</Label>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={addOption}
|
||||||
|
>
|
||||||
|
<Plus className="h-4 w-4 mr-1" />
|
||||||
|
옵션 추가
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead className="w-[150px]">키</TableHead>
|
||||||
|
<TableHead className="w-[150px]">이름</TableHead>
|
||||||
|
<TableHead className="w-[120px]">메시지 위치</TableHead>
|
||||||
|
<TableHead className="w-[120px]">메시지 길이</TableHead>
|
||||||
|
<TableHead>기본 값</TableHead>
|
||||||
|
<TableHead className="w-[80px]"></TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{(server.serverOptions || []).map((option, index) => (
|
||||||
|
<TableRow key={index}>
|
||||||
|
<TableCell>
|
||||||
|
<Input
|
||||||
|
value={option.optionKey}
|
||||||
|
onChange={(e) => updateOptionField(index, 'optionKey', e.target.value)}
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Input
|
||||||
|
value={option.optionName}
|
||||||
|
onChange={(e) => updateOptionField(index, 'optionName', e.target.value)}
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
value={option.offset}
|
||||||
|
onChange={(e) => updateOptionField(index, 'offset', parseInt(e.target.value))}
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
value={option.length}
|
||||||
|
onChange={(e) => updateOptionField(index, 'length', parseInt(e.target.value))}
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Input
|
||||||
|
value={option.defaultValue}
|
||||||
|
onChange={(e) => updateOptionField(index, 'defaultValue', e.target.value)}
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => removeOption(index)}
|
||||||
|
>
|
||||||
|
<Trash2 className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</ScrollArea>
|
||||||
|
|
||||||
|
<DialogFooter className="flex justify-end space-x-2">
|
||||||
|
<Button variant="secondary" onClick={() => handleDialogChange(false)}>
|
||||||
|
닫기
|
||||||
|
</Button>
|
||||||
|
<Button type="submit" variant="default" onClick={handleSubmit}>
|
||||||
|
저장
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ServerEditDialog;
|
||||||
|
|||||||
@@ -0,0 +1,127 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import DataTable from '@/components/shared/DataTable';
|
||||||
|
import { useAPI } from '@/providers/APIProvider';
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
import { Badge } from '@/components/ui/badge';
|
||||||
|
|
||||||
|
const ServerList = ({ onMenuClick }) => {
|
||||||
|
const {
|
||||||
|
serverList,
|
||||||
|
loadServerList,
|
||||||
|
deleteServers,
|
||||||
|
loading
|
||||||
|
} = useAPI();
|
||||||
|
|
||||||
|
const [searchTerm, setSearchTerm] = React.useState('');
|
||||||
|
const [selectedIds, setSelectedIds] = React.useState([]);
|
||||||
|
const [page, setPage] = React.useState(0);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
loadServerList({ page });
|
||||||
|
}, [page, loadServerList]);
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{ key: 'name', label: '이름' },
|
||||||
|
{
|
||||||
|
key: 'address',
|
||||||
|
label: '주소',
|
||||||
|
render: (_, row) => `${row.scheme}://${row.hostname}:${row.port}${row.basePath}`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'enabled',
|
||||||
|
label: '사용 중',
|
||||||
|
render: (value) => (
|
||||||
|
<Badge variant={value ? 'success' : 'destructive'}>
|
||||||
|
{value ? '사용' : '미사용'}
|
||||||
|
</Badge>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const handleSearch = () => {
|
||||||
|
setPage(0);
|
||||||
|
loadServerList({ name: searchTerm, page: 0 });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = async () => {
|
||||||
|
const success = await deleteServers(selectedIds);
|
||||||
|
if (success) {
|
||||||
|
setSelectedIds([]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderRowActions = (row) => (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
className="mr-2"
|
||||||
|
// onClick={() => onMenuClick('server-edit', null, {...row})}
|
||||||
|
onClick={() => {
|
||||||
|
console.log('Before onMenuClick:', row); // Debug log
|
||||||
|
onMenuClick('server-edit', null, row);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
수정
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
const tableActions = (
|
||||||
|
<>
|
||||||
|
<Button variant="destructive" onClick={handleSearch}>조회</Button>
|
||||||
|
<Button
|
||||||
|
variant="destructive"
|
||||||
|
onClick={handleDelete}
|
||||||
|
disabled={selectedIds.length === 0}
|
||||||
|
>
|
||||||
|
삭제
|
||||||
|
</Button>
|
||||||
|
<Button onClick={() => onMenuClick('server-add')}>등록</Button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto p-4">
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>서버 목록</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="flex items-center space-x-4 mb-4">
|
||||||
|
<div className="flex-1 max-w-sm">
|
||||||
|
<Input
|
||||||
|
placeholder="이름으로 검색"
|
||||||
|
value={searchTerm}
|
||||||
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
|
onKeyUp={(e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
handleSearch();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{tableActions}
|
||||||
|
</div>
|
||||||
|
<DataTable
|
||||||
|
data={serverList}
|
||||||
|
columns={columns}
|
||||||
|
loading={loading}
|
||||||
|
selectable
|
||||||
|
selectedIds={selectedIds}
|
||||||
|
onSelectIds={setSelectedIds}
|
||||||
|
onPageChange={setPage}
|
||||||
|
onDelete={handleDelete}
|
||||||
|
renderRowActions={renderRowActions}
|
||||||
|
deleteDialogTitle="서버 삭제"
|
||||||
|
deleteDialogDescription="선택한 서버를 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다."
|
||||||
|
/>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ServerList;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import AddAPIDialog from '@/components/api-tester/AddAPIDialog.jsx';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import AddAPIDialog from '@/components/api-tester/AddAPIDialog.jsx';
|
||||||
import AddCollectionDialog from '@/components/api-tester/AddCollectionDialog.jsx';
|
import AddCollectionDialog from '@/components/api-tester/AddCollectionDialog.jsx';
|
||||||
import AddScenarioDialog from '@/components/api-tester/AddScenarioDialog.jsx';
|
import AddScenarioDialog from '@/components/api-tester/AddScenarioDialog.jsx';
|
||||||
import VariableModal from '@/components/api-tester/VariableModal.jsx';
|
import VariableModal from '@/components/api-tester/VariableModal.jsx';
|
||||||
@@ -8,7 +8,71 @@ import TCPServerEditDialog from '@/components/tcp-servers/TCPServerEditDialog.js
|
|||||||
import QueueEditDialog from '@/components/queues/QueueEditDialog.jsx';
|
import QueueEditDialog from '@/components/queues/QueueEditDialog.jsx';
|
||||||
import UserEditDialog from '@/components/users/UserEditDialog.jsx';
|
import UserEditDialog from '@/components/users/UserEditDialog.jsx';
|
||||||
import PasswordChangeDialog from '@/components/account/PasswordChangeDialog.jsx';
|
import PasswordChangeDialog from '@/components/account/PasswordChangeDialog.jsx';
|
||||||
|
import ServerEditDialog from '@/components/servers/ServerEditDialog.jsx';
|
||||||
|
|
||||||
|
// const DialogManager = ({
|
||||||
|
// showAddApi,
|
||||||
|
// showAddCollection,
|
||||||
|
// showAddScenario,
|
||||||
|
// showVariableModal,
|
||||||
|
// showMockRouteEdit,
|
||||||
|
// showTCPServerEdit,
|
||||||
|
// showQueueEdit,
|
||||||
|
// showUserEdit,
|
||||||
|
// selectedItem,
|
||||||
|
// showPasswordChange,
|
||||||
|
// showServerEdit,
|
||||||
|
// onDialogChange
|
||||||
|
// }) => {
|
||||||
|
//
|
||||||
|
// return <>
|
||||||
|
// <AddCollectionDialog
|
||||||
|
// open={showAddCollection}
|
||||||
|
// onOpenChange={(isOpen) => onDialogChange('showAddCollection', isOpen)}
|
||||||
|
// />
|
||||||
|
// <AddAPIDialog
|
||||||
|
// open={showAddApi}
|
||||||
|
// onOpenChange={(isOpen) => onDialogChange('showAddApi', isOpen)}
|
||||||
|
// />
|
||||||
|
// <AddScenarioDialog
|
||||||
|
// open={showAddScenario}
|
||||||
|
// onOpenChange={(isOpen) => onDialogChange('showAddScenario', isOpen)}
|
||||||
|
// />
|
||||||
|
// <VariableModal
|
||||||
|
// open={showVariableModal}
|
||||||
|
// onOpenChange={(isOpen) => onDialogChange('showVariableModal', isOpen)}
|
||||||
|
// />
|
||||||
|
// <MockRouteEditDialog
|
||||||
|
// open={showMockRouteEdit}
|
||||||
|
// onOpenChange={(isOpen) => onDialogChange('showMockRouteEdit', isOpen)}
|
||||||
|
// item={selectedItem}
|
||||||
|
// />
|
||||||
|
// <TCPServerEditDialog
|
||||||
|
// open={showTCPServerEdit}
|
||||||
|
// onOpenChange={(isOpen) => onDialogChange('showTCPServerEdit', isOpen)}
|
||||||
|
// item={selectedItem}
|
||||||
|
// />
|
||||||
|
// <QueueEditDialog
|
||||||
|
// open={showQueueEdit}
|
||||||
|
// onOpenChange={(isOpen) => onDialogChange('showQueueEdit', isOpen)}
|
||||||
|
// item={selectedItem}
|
||||||
|
// />
|
||||||
|
// <UserEditDialog
|
||||||
|
// open={showUserEdit}
|
||||||
|
// onOpenChange={(isOpen) => onDialogChange('showUserEdit', isOpen)}
|
||||||
|
// item={selectedItem}
|
||||||
|
// />
|
||||||
|
// <PasswordChangeDialog
|
||||||
|
// open={showPasswordChange}
|
||||||
|
// onOpenChange={(isOpen) => onDialogChange('showPasswordChange', isOpen)}
|
||||||
|
// />
|
||||||
|
// <ServerEditDialog
|
||||||
|
// open={showServerEdit}
|
||||||
|
// onOpenChange={(isOpen) => onDialogChange('showServerEdit', isOpen)}
|
||||||
|
// item={selectedItem}
|
||||||
|
// />
|
||||||
|
// </>;
|
||||||
|
// }
|
||||||
const DialogManager = ({
|
const DialogManager = ({
|
||||||
showAddApi,
|
showAddApi,
|
||||||
showAddCollection,
|
showAddCollection,
|
||||||
@@ -18,53 +82,55 @@ const DialogManager = ({
|
|||||||
showTCPServerEdit,
|
showTCPServerEdit,
|
||||||
showQueueEdit,
|
showQueueEdit,
|
||||||
showUserEdit,
|
showUserEdit,
|
||||||
|
showServerEdit,
|
||||||
selectedItem,
|
selectedItem,
|
||||||
showPasswordChange,
|
|
||||||
onDialogChange
|
onDialogChange
|
||||||
}) => {
|
}) => {
|
||||||
|
return (
|
||||||
return <>
|
<>
|
||||||
<AddCollectionDialog
|
<AddCollectionDialog
|
||||||
open={showAddCollection}
|
open={showAddCollection}
|
||||||
onOpenChange={(isOpen) => onDialogChange('showAddCollection', isOpen)}
|
onOpenChange={(isOpen) => onDialogChange('showAddCollection', isOpen)}
|
||||||
/>
|
/>
|
||||||
<AddAPIDialog
|
<AddAPIDialog
|
||||||
open={showAddApi}
|
open={showAddApi}
|
||||||
onOpenChange={(isOpen) => onDialogChange('showAddApi', isOpen)}
|
onOpenChange={(isOpen) => onDialogChange('showAddApi', isOpen)}
|
||||||
/>
|
/>
|
||||||
<AddScenarioDialog
|
<AddScenarioDialog
|
||||||
open={showAddScenario}
|
open={showAddScenario}
|
||||||
onOpenChange={(isOpen) => onDialogChange('showAddScenario', isOpen)}
|
onOpenChange={(isOpen) => onDialogChange('showAddScenario', isOpen)}
|
||||||
/>
|
/>
|
||||||
<VariableModal
|
<VariableModal
|
||||||
open={showVariableModal}
|
open={showVariableModal}
|
||||||
onOpenChange={(isOpen) => onDialogChange('showVariableModal', isOpen)}
|
onOpenChange={(isOpen) => onDialogChange('showVariableModal', isOpen)}
|
||||||
/>
|
/>
|
||||||
<MockRouteEditDialog
|
<MockRouteEditDialog
|
||||||
open={showMockRouteEdit}
|
open={showMockRouteEdit}
|
||||||
onOpenChange={(isOpen) => onDialogChange('showMockRouteEdit', isOpen)}
|
onOpenChange={(isOpen) => onDialogChange('showMockRouteEdit', isOpen)}
|
||||||
item={selectedItem}
|
item={selectedItem}
|
||||||
/>
|
/>
|
||||||
<TCPServerEditDialog
|
<TCPServerEditDialog
|
||||||
open={showTCPServerEdit}
|
open={showTCPServerEdit}
|
||||||
onOpenChange={(isOpen) => onDialogChange('showTCPServerEdit', isOpen)}
|
onOpenChange={(isOpen) => onDialogChange('showTCPServerEdit', isOpen)}
|
||||||
item={selectedItem}
|
item={selectedItem}
|
||||||
/>
|
/>
|
||||||
<QueueEditDialog
|
<QueueEditDialog
|
||||||
open={showQueueEdit}
|
open={showQueueEdit}
|
||||||
onOpenChange={(isOpen) => onDialogChange('showQueueEdit', isOpen)}
|
onOpenChange={(isOpen) => onDialogChange('showQueueEdit', isOpen)}
|
||||||
item={selectedItem}
|
item={selectedItem}
|
||||||
/>
|
/>
|
||||||
<UserEditDialog
|
<UserEditDialog
|
||||||
open={showUserEdit}
|
open={showUserEdit}
|
||||||
onOpenChange={(isOpen) => onDialogChange('showUserEdit', isOpen)}
|
onOpenChange={(isOpen) => onDialogChange('showUserEdit', isOpen)}
|
||||||
item={selectedItem}
|
item={selectedItem}
|
||||||
/>
|
/>
|
||||||
<PasswordChangeDialog
|
<ServerEditDialog
|
||||||
open={showPasswordChange}
|
open={showServerEdit}
|
||||||
onOpenChange={(isOpen) => onDialogChange('showPasswordChange', isOpen)}
|
onOpenChange={(isOpen) => onDialogChange('showServerEdit', isOpen)}
|
||||||
/>
|
item={selectedItem}
|
||||||
</>;
|
/>
|
||||||
}
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default DialogManager;
|
export default DialogManager;
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ export const menuConfig = [
|
|||||||
label: '응답관리',
|
label: '응답관리',
|
||||||
items: [
|
items: [
|
||||||
{id: 'response-list', label: 'API 응답 목록', action: 'response-list', screen: 'response-list'},
|
{id: 'response-list', label: 'API 응답 목록', action: 'response-list', screen: 'response-list'},
|
||||||
{id: 'response-add', label: 'API 응답 추가', action: 'response-add'},
|
// {id: 'response-add', label: 'API 응답 추가', action: 'response-add'},
|
||||||
{type: 'separator'},
|
// {type: 'separator'},
|
||||||
{id: 'socket-list', label: 'TCP 서버 목록', action: 'socket-list', screen: 'socket-list'},
|
{id: 'socket-list', label: 'TCP 서버 목록', action: 'socket-list', screen: 'socket-list'},
|
||||||
{id: 'socket-add', label: 'TCP 서버 추가', action: 'socket-add'},
|
// {id: 'socket-add', label: 'TCP 서버 추가', action: 'socket-add'},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -47,9 +47,7 @@ export const menuConfig = [
|
|||||||
label: '시스템 관리',
|
label: '시스템 관리',
|
||||||
items: [
|
items: [
|
||||||
{id: 'user-list', label: '사용자 관리', action: 'user-list', screen: 'user-list'},
|
{id: 'user-list', label: '사용자 관리', action: 'user-list', screen: 'user-list'},
|
||||||
{type: 'separator'},
|
{id: 'server-list', label: '서버 관리', action: 'server-list', screen: 'server-list'},
|
||||||
{id: 'server-management', label: '서버 관리', action: 'server-management', screen: 'server-management'},
|
|
||||||
{id: 'server-add', label: '서버 추가', action: 'server-add', screen: 'server-add'},
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import MockRouteList from '../components/mock-route/MockRouteList.jsx';
|
|||||||
import TCPServerList from '@/components/tcp-servers/TCPServerList.jsx';
|
import TCPServerList from '@/components/tcp-servers/TCPServerList.jsx';
|
||||||
import QueueList from '@/components/queues/QueueList.jsx';
|
import QueueList from '@/components/queues/QueueList.jsx';
|
||||||
import UserList from '@/components/users/UserList.jsx';
|
import UserList from '@/components/users/UserList.jsx';
|
||||||
|
import ServerList from '@/components/servers/ServerList.jsx';
|
||||||
|
|
||||||
export const SCREENS = {
|
export const SCREENS = {
|
||||||
'api-tester': APITester,
|
'api-tester': APITester,
|
||||||
@@ -13,4 +14,5 @@ export const SCREENS = {
|
|||||||
'socket-list': TCPServerList,
|
'socket-list': TCPServerList,
|
||||||
'queue-list': QueueList,
|
'queue-list': QueueList,
|
||||||
'user-list': UserList,
|
'user-list': UserList,
|
||||||
|
'server-list': ServerList
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -52,6 +52,14 @@ export const APIProvider = ({
|
|||||||
last: true
|
last: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [serverList, setServerList] = useState({
|
||||||
|
content: [],
|
||||||
|
totalPages: 0,
|
||||||
|
number: 0,
|
||||||
|
first: true,
|
||||||
|
last: true
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
// Initialize services
|
// Initialize services
|
||||||
const collections$ = collectionService(contextPath);
|
const collections$ = collectionService(contextPath);
|
||||||
@@ -447,6 +455,66 @@ export const APIProvider = ({
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const loadServerList = useCallback(async (params = { page: 0 }) => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
try {
|
||||||
|
const data = await servers$.loadServerList(params);
|
||||||
|
setServerList(data);
|
||||||
|
return data;
|
||||||
|
} catch (err) {
|
||||||
|
setError(err.message);
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const deleteServers = useCallback(async (ids) => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
try {
|
||||||
|
await servers$.deleteServers(ids);
|
||||||
|
await loadServerList();
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
setError(err.message);
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, [loadServerList]);
|
||||||
|
|
||||||
|
const createServer = useCallback(async (server) => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
try {
|
||||||
|
const newServer = await servers$.createServer(server);
|
||||||
|
await loadServerList();
|
||||||
|
return newServer;
|
||||||
|
} catch (err) {
|
||||||
|
setError(err.message);
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, [loadServerList]);
|
||||||
|
|
||||||
|
const updateServer = useCallback(async (server) => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
try {
|
||||||
|
const updatedServer = await servers$.updateServer(server.id, server);
|
||||||
|
await loadServerList();
|
||||||
|
return updatedServer;
|
||||||
|
} catch (err) {
|
||||||
|
setError(err.message);
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, [loadServerList]);
|
||||||
|
|
||||||
const contextValue = {
|
const contextValue = {
|
||||||
apiClient,
|
apiClient,
|
||||||
collections,
|
collections,
|
||||||
@@ -454,6 +522,11 @@ export const APIProvider = ({
|
|||||||
servers,
|
servers,
|
||||||
loadServers,
|
loadServers,
|
||||||
getServer,
|
getServer,
|
||||||
|
serverList,
|
||||||
|
loadServerList,
|
||||||
|
deleteServers,
|
||||||
|
createServer,
|
||||||
|
updateServer,
|
||||||
loading,
|
loading,
|
||||||
error,
|
error,
|
||||||
loadCollections,
|
loadCollections,
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import {getCSRFToken} from '@/services/token.js';
|
||||||
|
|
||||||
export const serverService = () => {
|
export const serverService = () => {
|
||||||
return {
|
return {
|
||||||
async loadServers() {
|
async loadServers() {
|
||||||
@@ -13,6 +15,60 @@ export const serverService = () => {
|
|||||||
basePath: server?.basePath || '',
|
basePath: server?.basePath || '',
|
||||||
environment: server?.environment || 'unknown'
|
environment: server?.environment || 'unknown'
|
||||||
}));
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
async loadServerList(params = { page: 0, name: '' }) {
|
||||||
|
const searchParams = new URLSearchParams({
|
||||||
|
page: params.page,
|
||||||
|
size: 10,
|
||||||
|
...(params.name && { name: params.name })
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await fetch(`/servers?${searchParams}`);
|
||||||
|
if (!response.ok) throw new Error('Failed to load servers');
|
||||||
|
return response.json();
|
||||||
|
},
|
||||||
|
|
||||||
|
async createServer(server) {
|
||||||
|
const response = await fetch('/servers', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-XSRF-TOKEN': getCSRFToken()
|
||||||
|
},
|
||||||
|
body: JSON.stringify(server)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) throw new Error('Failed to create server');
|
||||||
|
return response.json();
|
||||||
|
},
|
||||||
|
|
||||||
|
async updateServer(id, server) {
|
||||||
|
const response = await fetch(`/servers/update/${id}`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-XSRF-TOKEN': getCSRFToken()
|
||||||
|
},
|
||||||
|
body: JSON.stringify(server)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) throw new Error('Failed to update server');
|
||||||
|
return response.json();
|
||||||
|
},
|
||||||
|
|
||||||
|
async deleteServers(ids) {
|
||||||
|
const response = await fetch('/servers/delete', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-XSRF-TOKEN': getCSRFToken()
|
||||||
|
},
|
||||||
|
body: JSON.stringify(ids)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) throw new Error('Failed to delete servers');
|
||||||
|
return response.json();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user