큐 관리 추가
This commit is contained in:
@@ -29,18 +29,22 @@ const AppContent = ({ options, showLogin, setShowLogin }) => {
|
|||||||
showAddScenario: false,
|
showAddScenario: false,
|
||||||
showVariableModal: false,
|
showVariableModal: false,
|
||||||
showMockRouteEdit: false,
|
showMockRouteEdit: false,
|
||||||
|
showTCPServerEdit: false,
|
||||||
|
showQueueEdit: false,
|
||||||
selectedItem: null,
|
selectedItem: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleMenuClick = async (action, screen, item = null) => {
|
const handleMenuClick = async (action, screen, item = null) => {
|
||||||
|
|
||||||
|
console.log(action);
|
||||||
|
console.log(item);
|
||||||
|
|
||||||
if (screen) {
|
if (screen) {
|
||||||
console.log('Screen:', screen);
|
console.log('Screen:', screen);
|
||||||
setCurrentScreen(screen);
|
setCurrentScreen(screen);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(item);
|
|
||||||
// Handle modal actions
|
// Handle modal actions
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case 'request-add':
|
case 'request-add':
|
||||||
@@ -49,11 +53,15 @@ const AppContent = ({ options, showLogin, setShowLogin }) => {
|
|||||||
case 'response-add':
|
case 'response-add':
|
||||||
case 'response-edit':
|
case 'response-edit':
|
||||||
case 'response-clone':
|
case 'response-clone':
|
||||||
setDialogState(prev => ({
|
setDialogState(prev => ({...prev, showMockRouteEdit: true, selectedItem: item }));
|
||||||
...prev,
|
break;
|
||||||
showMockRouteEdit: true,
|
case 'queue-add':
|
||||||
selectedItem: item // Pass the selected item
|
case 'queue-edit':
|
||||||
}));
|
setDialogState(prev => ({ ...prev, showQueueEdit: true , selectedItem: item}));
|
||||||
|
break;
|
||||||
|
case 'socket-add':
|
||||||
|
case 'socket-edit':
|
||||||
|
setDialogState(prev => ({ ...prev, showTCPServerEdit: true , selectedItem: item}));
|
||||||
break;
|
break;
|
||||||
case 'collection-add':
|
case 'collection-add':
|
||||||
setDialogState(prev => ({ ...prev, showAddCollection: true }));
|
setDialogState(prev => ({ ...prev, showAddCollection: true }));
|
||||||
@@ -122,6 +130,8 @@ const AppContent = ({ options, showLogin, setShowLogin }) => {
|
|||||||
showAddScenario={dialogState.showAddScenario}
|
showAddScenario={dialogState.showAddScenario}
|
||||||
showVariableModal={dialogState.showVariableModal}
|
showVariableModal={dialogState.showVariableModal}
|
||||||
showMockRouteEdit={dialogState.showMockRouteEdit}
|
showMockRouteEdit={dialogState.showMockRouteEdit}
|
||||||
|
showTCPServerEdit={dialogState.showTCPServerEdit}
|
||||||
|
showQueueEdit={dialogState.showQueueEdit}
|
||||||
selectedItem={dialogState.selectedItem}
|
selectedItem={dialogState.selectedItem}
|
||||||
onDialogChange={handleDialogChange}
|
onDialogChange={handleDialogChange}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -229,7 +229,7 @@ const MockRouteEditDialog = ({open, onOpenChange, item}) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
<DialogContent className="fixed translate-x-0 translate-y-0 overflow-y-auto inset-0 h-screen w-screen max-w-none">
|
<DialogContent className="max-w-full h-screen">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
{mockRoute.id == null ? 'API 정보 등록' : 'API 정보 수정'}
|
{mockRoute.id == null ? 'API 정보 등록' : 'API 정보 수정'}
|
||||||
|
|||||||
@@ -0,0 +1,173 @@
|
|||||||
|
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 { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
import { Label } from '@/components/ui/label';
|
||||||
|
import MonacoEditor from '@/components/shared/MonacoEditor';
|
||||||
|
import {getCSRFToken} from '@/services/token.js';
|
||||||
|
|
||||||
|
const defaultScript = `
|
||||||
|
//message를 사용 가능
|
||||||
|
var URL = Java.type('java.net.URL');
|
||||||
|
var HttpURLConnection = Java.type('java.net.HttpURLConnection');
|
||||||
|
var BufferedReader = Java.type('java.io.BufferedReader');
|
||||||
|
var InputStreamReader = Java.type('java.io.InputStreamReader');
|
||||||
|
var StringBuilder = Java.type('java.lang.StringBuilder');
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
var url = new URL('http://localhost:38080/');
|
||||||
|
var connection = url.openConnection();
|
||||||
|
connection.setRequestMethod('POST');
|
||||||
|
connection.setDoOutput(true);
|
||||||
|
|
||||||
|
// Write the body
|
||||||
|
var outputStream = connection.getOutputStream();
|
||||||
|
var body = 'async_result' + message;
|
||||||
|
outputStream.write(new java.lang.String(body).getBytes('UTF-8'));
|
||||||
|
outputStream.close();
|
||||||
|
|
||||||
|
console.log(body);
|
||||||
|
// Read the response
|
||||||
|
var responseCode = connection.getResponseCode();
|
||||||
|
if (responseCode === 200) {
|
||||||
|
var inputStream = connection.getInputStream();
|
||||||
|
var reader = new BufferedReader(new InputStreamReader(inputStream));
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
var line;
|
||||||
|
|
||||||
|
while ((line = reader.readLine()) !== null) {
|
||||||
|
builder.append(line);
|
||||||
|
builder.append('\\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.close();
|
||||||
|
console.log('POST Response:' + builder.toString());
|
||||||
|
} else {
|
||||||
|
console.log('HTTP Error! Status:' + responseCode);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log('Error making POST request:' + error);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const QueueEditDialog = ({ open, onOpenChange, item }) => {
|
||||||
|
const [queueHandler, setQueueHandler] = useState({
|
||||||
|
queueName: '',
|
||||||
|
handlerScript: defaultScript
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize form when item changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (item) {
|
||||||
|
setQueueHandler(item);
|
||||||
|
} else {
|
||||||
|
// Reset to default state when creating new
|
||||||
|
setQueueHandler({
|
||||||
|
queueName: '',
|
||||||
|
handlerScript: defaultScript
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [item]);
|
||||||
|
|
||||||
|
const handleSubmit = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (!queueHandler.queueName || !queueHandler.handlerScript) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const url = item ? `/api/queue-handlers/update/${item.queueName}` : '/api/queue-handlers';
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-XSRF-TOKEN': getCSRFToken()
|
||||||
|
},
|
||||||
|
body: JSON.stringify(queueHandler)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to save queue handler');
|
||||||
|
}
|
||||||
|
|
||||||
|
onOpenChange(false);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to save queue handler:', error);
|
||||||
|
alert('Failed to save queue handler');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChange = (field, value) => {
|
||||||
|
setQueueHandler(prev => ({ ...prev, [field]: value }));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
|
<DialogContent className="max-w-full">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>
|
||||||
|
{item ? '큐 핸들러 수정' : '큐 핸들러 등록'}
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
큐 핸들러의 정보를 입력하거나 수정할 수 있습니다. 필수 항목을 모두 입력해주세요.
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<ScrollArea className="max-h-[80vh]">
|
||||||
|
<form id="queueForm" onSubmit={handleSubmit} className="space-y-4 p-1">
|
||||||
|
{/* Queue Name */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="queueName" className="must">
|
||||||
|
큐 이름
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="queueName"
|
||||||
|
required
|
||||||
|
value={queueHandler.queueName}
|
||||||
|
onChange={(e) => handleChange('queueName', e.target.value)}
|
||||||
|
disabled={!!item} // Disable editing of queue name for existing handlers
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Handler Script */}
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>핸들러 스크립트</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="h-[400px]">
|
||||||
|
<MonacoEditor
|
||||||
|
value={queueHandler.handlerScript}
|
||||||
|
language="javascript"
|
||||||
|
onChange={(value) => handleChange('handlerScript', value)}
|
||||||
|
height="full"
|
||||||
|
/>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</form>
|
||||||
|
</ScrollArea>
|
||||||
|
|
||||||
|
<DialogFooter className="flex justify-end space-x-2">
|
||||||
|
<Button variant="secondary" onClick={() => onOpenChange(false)}>
|
||||||
|
닫기
|
||||||
|
</Button>
|
||||||
|
<Button type="submit" variant="default" onClick={handleSubmit}>
|
||||||
|
저장
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default QueueEditDialog;
|
||||||
|
|||||||
@@ -0,0 +1,126 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import DataTable from '@/components/shared/DataTable';
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
import { Badge } from '@/components/ui/badge';
|
||||||
|
import { useAPI } from '@/providers/APIProvider';
|
||||||
|
|
||||||
|
const QueueList = ({ onMenuClick }) => {
|
||||||
|
const { queues, loading, loadQueues, deleteQueues, startQueue, stopQueue } = useAPI();
|
||||||
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
|
const [selectedIds, setSelectedIds] = useState([]);
|
||||||
|
const [page, setPage] = useState(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadQueues({ page });
|
||||||
|
}, [page, loadQueues]);
|
||||||
|
|
||||||
|
const handleSearch = () => {
|
||||||
|
setPage(0);
|
||||||
|
loadQueues({ page: 0, queueName: searchTerm });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = async () => {
|
||||||
|
if (!selectedIds.length) return;
|
||||||
|
const success = await deleteQueues(selectedIds);
|
||||||
|
if (success) {
|
||||||
|
setSelectedIds([]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{ key: 'queueName', label: '큐 이름' },
|
||||||
|
{
|
||||||
|
key: 'active',
|
||||||
|
label: '상태',
|
||||||
|
render: (value) => (
|
||||||
|
<Badge variant={value ? 'success' : 'destructive'}>
|
||||||
|
{value ? 'Active' : 'Inactive'}
|
||||||
|
</Badge>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const renderRowActions = (row) => (
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => startQueue(row.queueName)}
|
||||||
|
>
|
||||||
|
시작
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => stopQueue(row.queueName)}
|
||||||
|
>
|
||||||
|
정지
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => onMenuClick('queue-edit', null, row)}
|
||||||
|
>
|
||||||
|
수정
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const tableActions = (
|
||||||
|
<>
|
||||||
|
<Button variant="destructive" onClick={handleSearch}>조회</Button>
|
||||||
|
<Button
|
||||||
|
variant="destructive"
|
||||||
|
onClick={handleDelete}
|
||||||
|
disabled={selectedIds.length === 0}
|
||||||
|
>
|
||||||
|
삭제
|
||||||
|
</Button>
|
||||||
|
<Button onClick={() => onMenuClick('queue-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={queues}
|
||||||
|
columns={columns}
|
||||||
|
loading={loading}
|
||||||
|
selectable
|
||||||
|
selectedIds={selectedIds}
|
||||||
|
onSelectIds={setSelectedIds}
|
||||||
|
onPageChange={setPage}
|
||||||
|
onDelete={handleDelete}
|
||||||
|
renderRowActions={renderRowActions}
|
||||||
|
deleteDialogTitle="큐 삭제"
|
||||||
|
deleteDialogDescription="선택한 큐를 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다."
|
||||||
|
/>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default QueueList;
|
||||||
|
|||||||
@@ -1,9 +1,18 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {Table, TableBody, TableCell, TableHead, TableHeader, TableRow} from '@/components/ui/table';
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
||||||
import {Button} from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import {Card, CardContent} from '@/components/ui/card';
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
import {Checkbox} from '@/components/ui/checkbox';
|
import { Checkbox } from '@/components/ui/checkbox';
|
||||||
import {AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle} from '@/components/ui/alert-dialog';
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogAction,
|
||||||
|
AlertDialogCancel,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogTitle
|
||||||
|
} from '@/components/ui/alert-dialog';
|
||||||
|
|
||||||
const DataTable = ({
|
const DataTable = ({
|
||||||
// Data and columns
|
// Data and columns
|
||||||
@@ -28,34 +37,33 @@ const DataTable = ({
|
|||||||
deleteDialogDescription = 'Are you sure you want to delete the selected items? This action cannot be undone.',
|
deleteDialogDescription = 'Are you sure you want to delete the selected items? This action cannot be undone.',
|
||||||
onDelete
|
onDelete
|
||||||
}) => {
|
}) => {
|
||||||
|
|
||||||
const [showDeleteDialog, setShowDeleteDialog] = React.useState(false);
|
const [showDeleteDialog, setShowDeleteDialog] = React.useState(false);
|
||||||
|
|
||||||
const handleSelectAll = (checked) => {
|
const handleSelectAll = React.useCallback((checked) => {
|
||||||
if (!onSelectIds) return;
|
if (!onSelectIds) return;
|
||||||
if (checked) {
|
if (checked) {
|
||||||
onSelectIds(data.content.map(item => item.id));
|
onSelectIds(data.content.map(item => item.id || item.queueName));
|
||||||
} else {
|
} else {
|
||||||
onSelectIds([]);
|
onSelectIds([]);
|
||||||
}
|
}
|
||||||
};
|
}, [data.content, onSelectIds]);
|
||||||
|
|
||||||
const handleSelectItem = (id, checked) => {
|
const handleSelectItem = React.useCallback((id, checked) => {
|
||||||
if (!onSelectIds) return;
|
if (!onSelectIds) return;
|
||||||
if (checked) {
|
if (checked) {
|
||||||
onSelectIds([...selectedIds, id]);
|
onSelectIds([...selectedIds, id]);
|
||||||
} else {
|
} else {
|
||||||
onSelectIds(selectedIds.filter(selectedId => selectedId !== id));
|
onSelectIds(selectedIds.filter(selectedId => selectedId !== id));
|
||||||
}
|
}
|
||||||
};
|
}, [selectedIds, onSelectIds]);
|
||||||
|
|
||||||
const handlePageChange = (page) => {
|
const handlePageChange = React.useCallback((page) => {
|
||||||
// Reset selections when changing pages
|
// Reset selections when changing pages
|
||||||
if (onSelectIds) {
|
if (onSelectIds) {
|
||||||
onSelectIds([]);
|
onSelectIds([]);
|
||||||
}
|
}
|
||||||
onPageChange(page);
|
onPageChange(page);
|
||||||
};
|
}, [onSelectIds, onPageChange]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
@@ -82,34 +90,37 @@ const DataTable = ({
|
|||||||
</TableHead>
|
</TableHead>
|
||||||
)}
|
)}
|
||||||
{columns.map((column) => (
|
{columns.map((column) => (
|
||||||
<TableHead key={column.key}>{column.label}</TableHead>
|
<TableHead key={column.id || column.key}>{column.label}</TableHead>
|
||||||
))}
|
))}
|
||||||
{renderRowActions && <TableHead className="text-right">Actions</TableHead>}
|
{renderRowActions && <TableHead className="text-center">Actions</TableHead>}
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{data.content.map((row) => (
|
{data.content.map((row) => {
|
||||||
<TableRow key={row.id}>
|
const rowId = row.id || row.queueName;
|
||||||
{selectable && (
|
return (
|
||||||
<TableCell>
|
<TableRow key={rowId}>
|
||||||
<Checkbox
|
{selectable && (
|
||||||
checked={selectedIds.includes(row.id)}
|
<TableCell>
|
||||||
onCheckedChange={(checked) => handleSelectItem(row.id, checked)}
|
<Checkbox
|
||||||
/>
|
checked={selectedIds.includes(rowId)}
|
||||||
</TableCell>
|
onCheckedChange={(checked) => handleSelectItem(rowId, checked)}
|
||||||
)}
|
/>
|
||||||
{columns.map((column) => (
|
</TableCell>
|
||||||
<TableCell key={column.key}>
|
)}
|
||||||
{column.render ? column.render(row[column.key], row) : row[column.key]}
|
{columns.map((column) => (
|
||||||
</TableCell>
|
<TableCell key={`${rowId}-${column.id || column.key}`}>
|
||||||
))}
|
{column.render ? column.render(row[column.key], row) : row[column.key]}
|
||||||
{renderRowActions && (
|
</TableCell>
|
||||||
<TableCell className="text-right">
|
))}
|
||||||
{renderRowActions(row)}
|
{renderRowActions && (
|
||||||
</TableCell>
|
<TableCell className="text-center">
|
||||||
)}
|
{renderRowActions(row)}
|
||||||
</TableRow>
|
</TableCell>
|
||||||
))}
|
)}
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
</div>
|
</div>
|
||||||
@@ -117,7 +128,7 @@ const DataTable = ({
|
|||||||
{onPageChange && (
|
{onPageChange && (
|
||||||
<div className="flex justify-between items-center mt-4">
|
<div className="flex justify-between items-center mt-4">
|
||||||
<div className="text-sm text-gray-500">
|
<div className="text-sm text-gray-500">
|
||||||
Showing {data.numberOfElements} of {data.totalElements} results
|
전체 {data.totalElements} 중 {data.numberOfElements} 표시
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Button
|
<Button
|
||||||
@@ -134,12 +145,12 @@ const DataTable = ({
|
|||||||
disabled={data.first}
|
disabled={data.first}
|
||||||
size="sm"
|
size="sm"
|
||||||
>
|
>
|
||||||
Previous
|
이전
|
||||||
</Button>
|
</Button>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
{Array.from({ length: data.totalPages }, (_, i) => (
|
{Array.from({ length: data.totalPages }, (_, i) => (
|
||||||
<Button
|
<Button
|
||||||
key={i}
|
key={`page-${i}`}
|
||||||
variant={i === data.number ? "default" : "outline"}
|
variant={i === data.number ? "default" : "outline"}
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => handlePageChange(i)}
|
onClick={() => handlePageChange(i)}
|
||||||
@@ -155,7 +166,7 @@ const DataTable = ({
|
|||||||
disabled={data.last}
|
disabled={data.last}
|
||||||
size="sm"
|
size="sm"
|
||||||
>
|
>
|
||||||
Next
|
다음
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ 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';
|
||||||
import MockRouteEditDialog from '@/components/mock-route/MockRouteEditDialog.jsx';
|
import MockRouteEditDialog from '@/components/mock-route/MockRouteEditDialog.jsx';
|
||||||
|
import TCPServerEditDialog from '@/components/tcp-servers/TCPServerEditDialog.jsx';
|
||||||
|
import QueueEditDialog from '@/components/queues/QueueEditDialog.jsx';
|
||||||
|
|
||||||
const DialogManager = ({
|
const DialogManager = ({
|
||||||
showAddApi,
|
showAddApi,
|
||||||
@@ -11,6 +13,8 @@ const DialogManager = ({
|
|||||||
showAddScenario,
|
showAddScenario,
|
||||||
showVariableModal,
|
showVariableModal,
|
||||||
showMockRouteEdit,
|
showMockRouteEdit,
|
||||||
|
showTCPServerEdit,
|
||||||
|
showQueueEdit,
|
||||||
selectedItem,
|
selectedItem,
|
||||||
onDialogChange
|
onDialogChange
|
||||||
}) => {
|
}) => {
|
||||||
@@ -37,6 +41,16 @@ const DialogManager = ({
|
|||||||
onOpenChange={(isOpen) => onDialogChange('showMockRouteEdit', isOpen)}
|
onOpenChange={(isOpen) => onDialogChange('showMockRouteEdit', isOpen)}
|
||||||
item={selectedItem}
|
item={selectedItem}
|
||||||
/>
|
/>
|
||||||
|
<TCPServerEditDialog
|
||||||
|
open={showTCPServerEdit}
|
||||||
|
onOpenChange={(isOpen) => onDialogChange('showTCPServerEdit', isOpen)}
|
||||||
|
item={selectedItem}
|
||||||
|
/>
|
||||||
|
<QueueEditDialog
|
||||||
|
open={showQueueEdit}
|
||||||
|
onOpenChange={(isOpen) => onDialogChange('showQueueEdit', isOpen)}
|
||||||
|
item={selectedItem}
|
||||||
|
/>
|
||||||
</>;
|
</>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -142,9 +142,19 @@ const MonacoEditor = memo(({
|
|||||||
}, [normalizedValue]);
|
}, [normalizedValue]);
|
||||||
|
|
||||||
// Convert height to pixel value if it's a number
|
// Convert height to pixel value if it's a number
|
||||||
const containerHeight = typeof height === 'number'
|
// const containerHeight = typeof height === 'number'
|
||||||
? `${height}px`
|
// ? `${height}px`
|
||||||
: height.endsWith('px') ? height : `${height}px`;
|
// : height.endsWith('px') ? height : `${height}px`;
|
||||||
|
|
||||||
|
const getHeightStyle = (height) => {
|
||||||
|
if (height === 'full') {
|
||||||
|
return '100%';
|
||||||
|
}
|
||||||
|
if (typeof height === 'number') {
|
||||||
|
return `${height}px`;
|
||||||
|
}
|
||||||
|
return height.endsWith('px') ? height : `${height}px`;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -152,7 +162,7 @@ const MonacoEditor = memo(({
|
|||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
style={{
|
style={{
|
||||||
minHeight: '100px',
|
minHeight: '100px',
|
||||||
height: containerHeight,
|
height: getHeightStyle(height),
|
||||||
border: '1px solid #e2e8f0',
|
border: '1px solid #e2e8f0',
|
||||||
marginTop: '0.5rem'
|
marginTop: '0.5rem'
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -0,0 +1,165 @@
|
|||||||
|
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 { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
import { Label } from '@/components/ui/label';
|
||||||
|
import { Checkbox } from '@/components/ui/checkbox';
|
||||||
|
import MonacoEditor from '@/components/shared/MonacoEditor';
|
||||||
|
|
||||||
|
const defaultScript = `function processJson(message) {
|
||||||
|
//process message;
|
||||||
|
|
||||||
|
return "response_message";
|
||||||
|
}`;
|
||||||
|
|
||||||
|
const TCPServerEditDialog = ({ open, onOpenChange, item }) => {
|
||||||
|
const [server, setServer] = useState({
|
||||||
|
id: null,
|
||||||
|
name: '',
|
||||||
|
port: 1024,
|
||||||
|
active: true,
|
||||||
|
script: defaultScript
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize form when item changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (item) {
|
||||||
|
setServer(item);
|
||||||
|
} else {
|
||||||
|
// Reset to default state when creating new
|
||||||
|
setServer({
|
||||||
|
id: null,
|
||||||
|
name: '',
|
||||||
|
port: 1024,
|
||||||
|
active: true,
|
||||||
|
script: defaultScript
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [item]);
|
||||||
|
|
||||||
|
const handleSubmit = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (!server.name || !server.port) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const url = server.id ? `/api/socket/update/${server.id}` : '/api/socket';
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-XSRF-TOKEN': document.querySelector('meta[name="_csrf"]')?.getAttribute('content')
|
||||||
|
},
|
||||||
|
body: JSON.stringify(server)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to save server');
|
||||||
|
}
|
||||||
|
|
||||||
|
onOpenChange(false);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to save server:', error);
|
||||||
|
alert('Failed to save server');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChange = (field, value) => {
|
||||||
|
setServer(prev => ({ ...prev, [field]: value }));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
|
<DialogContent className="max-w-3xl">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>
|
||||||
|
{server.id == null ? 'TCP 서버 등록' : 'TCP 서버 수정'}
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
TCP 서버의 정보를 입력하거나 수정할 수 있습니다. 필수 항목을 모두 입력해주세요.
|
||||||
|
</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>
|
||||||
|
|
||||||
|
{/* Port Number */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="port" className="must">
|
||||||
|
포트
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="port"
|
||||||
|
type="number"
|
||||||
|
required
|
||||||
|
min="1024"
|
||||||
|
max="65535"
|
||||||
|
value={server.port}
|
||||||
|
onChange={(e) => handleChange('port', parseInt(e.target.value))}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Active Status */}
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Checkbox
|
||||||
|
id="active"
|
||||||
|
checked={server.active}
|
||||||
|
onCheckedChange={(checked) => handleChange('active', checked)}
|
||||||
|
/>
|
||||||
|
<Label htmlFor="active">Active</Label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Script Editor */}
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>서버 스크립트</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="h-[400px]">
|
||||||
|
<MonacoEditor
|
||||||
|
value={server.script}
|
||||||
|
language="javascript"
|
||||||
|
onChange={(value) => handleChange('script', value)}
|
||||||
|
height="full"
|
||||||
|
/>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</form>
|
||||||
|
</ScrollArea>
|
||||||
|
|
||||||
|
<DialogFooter className="flex justify-end space-x-2">
|
||||||
|
<Button variant="secondary" onClick={() => onOpenChange(false)}>
|
||||||
|
닫기
|
||||||
|
</Button>
|
||||||
|
<Button type="submit" variant="default" onClick={handleSubmit}>
|
||||||
|
저장
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TCPServerEditDialog;
|
||||||
|
|||||||
@@ -0,0 +1,209 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import DataTable from '@/components/shared/DataTable';
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
import { Badge } from '@/components/ui/badge';
|
||||||
|
import { format } from 'date-fns';
|
||||||
|
|
||||||
|
const TCPServerList = ({ onMenuClick }) => {
|
||||||
|
const [servers, setServers] = useState({
|
||||||
|
content: [],
|
||||||
|
totalPages: 0,
|
||||||
|
number: 0,
|
||||||
|
first: true,
|
||||||
|
last: true,
|
||||||
|
numberOfElements: 0,
|
||||||
|
totalElements: 0
|
||||||
|
});
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
|
const [selectedIds, setSelectedIds] = useState([]);
|
||||||
|
const [page, setPage] = useState(0);
|
||||||
|
|
||||||
|
const loadServers = async (params = { page: 0 }) => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const searchParams = new URLSearchParams({
|
||||||
|
page: params.page,
|
||||||
|
size: 10,
|
||||||
|
...(searchTerm && { name: searchTerm })
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await fetch(`/api/socket?${searchParams}`);
|
||||||
|
const data = await response.json();
|
||||||
|
setServers(data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading servers:', error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadServers({ page });
|
||||||
|
}, [page]);
|
||||||
|
|
||||||
|
const handleSearch = () => {
|
||||||
|
setPage(0);
|
||||||
|
loadServers({ page: 0 });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = async () => {
|
||||||
|
if (!selectedIds.length) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/socket/delete', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-XSRF-TOKEN': document.querySelector('meta[name="_csrf"]')?.getAttribute('content')
|
||||||
|
},
|
||||||
|
body: JSON.stringify(selectedIds)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
setSelectedIds([]);
|
||||||
|
loadServers({ page });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting servers:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleStartServer = async (id) => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/socket/${id}/start`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'X-XSRF-TOKEN': document.querySelector('meta[name="_csrf"]')?.getAttribute('content')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
loadServers({ page });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error starting server:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleStopServer = async (id) => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/socket/${id}/stop`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'X-XSRF-TOKEN': document.querySelector('meta[name="_csrf"]')?.getAttribute('content')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
loadServers({ page });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error stopping server:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{ key: 'name', label: 'Name' },
|
||||||
|
{ key: 'port', label: 'Port' },
|
||||||
|
{
|
||||||
|
key: 'active',
|
||||||
|
label: 'Status',
|
||||||
|
render: (value) => (
|
||||||
|
<Badge variant={value ? 'success' : 'destructive'}>
|
||||||
|
{value ? 'Active' : 'Inactive'}
|
||||||
|
</Badge>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'lastModified',
|
||||||
|
label: 'Last Modified',
|
||||||
|
render: (value) => format(new Date(value), 'yyyy-MM-dd HH:mm:ss')
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const renderRowActions = (row) => (
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
disabled={!row.active}
|
||||||
|
onClick={() => handleStartServer(row.id)}
|
||||||
|
>
|
||||||
|
Start
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => handleStopServer(row.id)}
|
||||||
|
>
|
||||||
|
Stop
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => onMenuClick('socket-edit', null, row)}
|
||||||
|
>
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const tableActions = (
|
||||||
|
<>
|
||||||
|
<Button variant="destructive" onClick={handleSearch}>조회</Button>
|
||||||
|
<Button
|
||||||
|
variant="destructive"
|
||||||
|
onClick={handleDelete}
|
||||||
|
disabled={selectedIds.length === 0}
|
||||||
|
>
|
||||||
|
삭제
|
||||||
|
</Button>
|
||||||
|
<Button onClick={() => onMenuClick('socket-add')}>등록</Button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto p-4">
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>TCP Server List</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={servers}
|
||||||
|
columns={columns}
|
||||||
|
loading={loading}
|
||||||
|
selectable
|
||||||
|
selectedIds={selectedIds}
|
||||||
|
onSelectIds={setSelectedIds}
|
||||||
|
onPageChange={setPage}
|
||||||
|
onDelete={handleDelete}
|
||||||
|
renderRowActions={renderRowActions}
|
||||||
|
deleteDialogTitle="서버 삭제"
|
||||||
|
deleteDialogDescription="선택한 서버를 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다."
|
||||||
|
/>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TCPServerList;
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ export const menuConfig = [
|
|||||||
{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: 'tcp-list', label: 'TCP 서버 목록', action: 'tcp-list'},
|
{id: 'socket-list', label: 'TCP 서버 목록', action: 'socket-list', screen: 'socket-list'},
|
||||||
{id: 'tcp-add', label: 'TCP 서버 추가', action: 'tcp-add'},
|
{id: 'socket-add', label: 'TCP 서버 추가', action: 'socket-add'},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -29,7 +29,7 @@ export const menuConfig = [
|
|||||||
label: '큐 관리',
|
label: '큐 관리',
|
||||||
items: [
|
items: [
|
||||||
{id: 'queue-list', label: '큐 목록', action: 'queue-list', screen: 'queue-list'},
|
{id: 'queue-list', label: '큐 목록', action: 'queue-list', screen: 'queue-list'},
|
||||||
{id: 'queue-add', label: '큐 추가', action: 'queue-add', screen: 'queue-add'}
|
{id: 'queue-add', label: '큐 추가', action: 'queue-add'}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,9 +2,13 @@
|
|||||||
import APITester from '../components/api-tester/APITester';
|
import APITester from '../components/api-tester/APITester';
|
||||||
import LoadTestManager from '../components/load-tester/LoadTestManager.jsx';
|
import LoadTestManager from '../components/load-tester/LoadTestManager.jsx';
|
||||||
import MockRouteList from '../components/mock-route/MockRouteList.jsx';
|
import MockRouteList from '../components/mock-route/MockRouteList.jsx';
|
||||||
|
import TCPServerList from '@/components/tcp-servers/TCPServerList.jsx';
|
||||||
|
import QueueList from '@/components/queues/QueueList.jsx';
|
||||||
|
|
||||||
export const SCREENS = {
|
export const SCREENS = {
|
||||||
'api-tester': APITester,
|
'api-tester': APITester,
|
||||||
'load-test': LoadTestManager,
|
'load-test': LoadTestManager,
|
||||||
'response-list': MockRouteList,
|
'response-list': MockRouteList,
|
||||||
|
'socket-list': TCPServerList,
|
||||||
|
'queue-list': QueueList,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {apiRequestService} from '@/services/apiRequestService';
|
|||||||
import {serverService} from '@/services/serverService';
|
import {serverService} from '@/services/serverService';
|
||||||
import {authService} from '@/services/authService';
|
import {authService} from '@/services/authService';
|
||||||
import APIClient from '@/services/APIClient.js';
|
import APIClient from '@/services/APIClient.js';
|
||||||
|
import {queueService} from '@/services/queueService.js';
|
||||||
|
|
||||||
const APIContext = createContext(null);
|
const APIContext = createContext(null);
|
||||||
|
|
||||||
@@ -34,11 +35,21 @@ export const APIProvider = ({
|
|||||||
last: true
|
last: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [queues, setQueues] = useState({
|
||||||
|
content: [],
|
||||||
|
totalPages: 0,
|
||||||
|
number: 0,
|
||||||
|
first: true,
|
||||||
|
last: true
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
// Initialize services
|
// Initialize services
|
||||||
const collections$ = collectionService(contextPath);
|
const collections$ = collectionService(contextPath);
|
||||||
const apiRequests$ = apiRequestService(contextPath);
|
const apiRequests$ = apiRequestService(contextPath);
|
||||||
const servers$ = serverService();
|
const servers$ = serverService();
|
||||||
const auth$ = authService();
|
const auth$ = authService();
|
||||||
|
const queues$ = queueService(contextPath);
|
||||||
|
|
||||||
const handleLogout = useCallback(async () => {
|
const handleLogout = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
@@ -292,6 +303,67 @@ export const APIProvider = ({
|
|||||||
}, [loadMockRoutes]);
|
}, [loadMockRoutes]);
|
||||||
|
|
||||||
|
|
||||||
|
const loadQueues = useCallback(async (params = { page: 0 }) => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
try {
|
||||||
|
const data = await queues$.loadQueues(params);
|
||||||
|
setQueues(data);
|
||||||
|
return data;
|
||||||
|
} catch (err) {
|
||||||
|
setError(err.message);
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const deleteQueues = useCallback(async (ids) => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
try {
|
||||||
|
await queues$.deleteQueues(ids);
|
||||||
|
await loadQueues();
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
setError(err.message);
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, [loadQueues]);
|
||||||
|
|
||||||
|
const startQueue = useCallback(async (id) => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
try {
|
||||||
|
await queues$.startQueue(id);
|
||||||
|
await loadQueues();
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
setError(err.message);
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, [loadQueues]);
|
||||||
|
|
||||||
|
const stopQueue = useCallback(async (id) => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
try {
|
||||||
|
await queues$.stopQueue(id);
|
||||||
|
await loadQueues();
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
setError(err.message);
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, [loadQueues]);
|
||||||
|
|
||||||
|
|
||||||
const contextValue = {
|
const contextValue = {
|
||||||
apiClient,
|
apiClient,
|
||||||
collections,
|
collections,
|
||||||
@@ -316,7 +388,12 @@ export const APIProvider = ({
|
|||||||
loadMockRoutes,
|
loadMockRoutes,
|
||||||
deleteMockRoutes,
|
deleteMockRoutes,
|
||||||
createMockRoute,
|
createMockRoute,
|
||||||
updateMockRoute
|
updateMockRoute,
|
||||||
|
queues,
|
||||||
|
loadQueues,
|
||||||
|
deleteQueues,
|
||||||
|
startQueue,
|
||||||
|
stopQueue
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
// services/queueService.js
|
||||||
|
import { getCSRFToken } from '@/services/token.js';
|
||||||
|
|
||||||
|
export const queueService = (contextPath = '/') => {
|
||||||
|
const headers = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-XSRF-TOKEN': getCSRFToken()
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
async loadQueues(params = { page: 0 }) {
|
||||||
|
const searchParams = new URLSearchParams({
|
||||||
|
page: params.page,
|
||||||
|
size: 10,
|
||||||
|
...(params.queueName && { queueName: params.queueName })
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await fetch(`${contextPath}api/queue-handlers?${searchParams}`);
|
||||||
|
if (!response.ok) throw new Error('Failed to fetch queues');
|
||||||
|
return await response.json();
|
||||||
|
},
|
||||||
|
|
||||||
|
async createQueue(queue) {
|
||||||
|
const response = await fetch(`${contextPath}api/queue-handlers`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers,
|
||||||
|
body: JSON.stringify(queue)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) throw new Error('Failed to create queue');
|
||||||
|
return await response.json();
|
||||||
|
},
|
||||||
|
|
||||||
|
async updateQueue(id, queue) {
|
||||||
|
const response = await fetch(`${contextPath}api/queue-handlers/update/${id}`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers,
|
||||||
|
body: JSON.stringify(queue)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) throw new Error('Failed to update queue');
|
||||||
|
return await response.json();
|
||||||
|
},
|
||||||
|
|
||||||
|
async deleteQueues(ids) {
|
||||||
|
const response = await fetch(`${contextPath}api/queue-handlers/delete`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers,
|
||||||
|
body: JSON.stringify(ids)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) throw new Error('Failed to delete queues');
|
||||||
|
},
|
||||||
|
|
||||||
|
async startQueue(id) {
|
||||||
|
const response = await fetch(`${contextPath}api/queue-handlers/${id}/start`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) throw new Error('Failed to start queue');
|
||||||
|
},
|
||||||
|
|
||||||
|
async stopQueue(id) {
|
||||||
|
const response = await fetch(`${contextPath}api/queue-handlers/${id}/stop`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) throw new Error('Failed to stop queue');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user