refactor: 서버 관리 UI 개선 - 접힘/펼침 + 선택 방식
- 실행 중인 서버 상태 카드 표시 - 서버 목록 접힘/펼침 가능 - 라디오 버튼 선택 방식으로 변경 - 선택 후 시작 버튼 하나로 통합
This commit is contained in:
parent
a1956a8ed5
commit
e6bd442aa4
1 changed files with 131 additions and 87 deletions
|
|
@ -108,6 +108,10 @@ export default function Admin({ isMobile = false }) {
|
|||
window.location.hash = tab;
|
||||
};
|
||||
|
||||
// 서버 관리 상태
|
||||
const [isServerListExpanded, setIsServerListExpanded] = useState(false); // 서버 목록 펼침 상태
|
||||
const [selectedServer, setSelectedServer] = useState(null); // 선택된 서버 경로
|
||||
|
||||
// 콘솔 관련 상태
|
||||
const [logs, setLogs] = useState([]);
|
||||
const [command, setCommand] = useState("");
|
||||
|
|
@ -1487,70 +1491,110 @@ export default function Admin({ isMobile = false }) {
|
|||
className="space-y-4"
|
||||
>
|
||||
{/* 서버 관리 */}
|
||||
{(() => {
|
||||
// 서버 목록 (하드코딩 - 나중에 API로 대체)
|
||||
const servers = [
|
||||
{ id: '1.21.1', loader: 'NeoForge', version: '1.21.1', path: 'neoforge/1.21.1/1.21.1', running: true },
|
||||
{ id: '1.21.1 create private', loader: 'NeoForge', version: '1.21.1', path: 'neoforge/1.21.1/1.21.1 create private', running: false },
|
||||
{ id: 'test', loader: 'NeoForge', version: '1.21.1', path: 'neoforge/1.21.1/test', running: false },
|
||||
{ id: '1.21.1 medieval', loader: 'Fabric', version: '1.21.1', path: 'fabric/1.21.1 medieval', running: false },
|
||||
];
|
||||
const runningServer = servers.find(s => s.running);
|
||||
|
||||
return (
|
||||
<div className="bg-zinc-900 border border-zinc-800 rounded-2xl p-4">
|
||||
<h3 className="text-white font-medium mb-3 flex items-center gap-2">
|
||||
{/* 헤더 - 실행 중인 서버 표시 */}
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<h3 className="text-white font-medium flex items-center gap-2">
|
||||
🖥️ 서버 관리
|
||||
</h3>
|
||||
<div className="space-y-2">
|
||||
{/* 서버 목록 - 하드코딩 (나중에 API로 대체) */}
|
||||
{[
|
||||
{
|
||||
id: "1.21.1",
|
||||
loader: "NeoForge",
|
||||
version: "1.21.1",
|
||||
path: "neoforge/1.21.1/1.21.1",
|
||||
running: true,
|
||||
},
|
||||
{
|
||||
id: "1.21.1 create private",
|
||||
loader: "NeoForge",
|
||||
version: "1.21.1",
|
||||
path: "neoforge/1.21.1/1.21.1 create private",
|
||||
running: false,
|
||||
},
|
||||
{
|
||||
id: "test",
|
||||
loader: "NeoForge",
|
||||
version: "1.21.1",
|
||||
path: "neoforge/1.21.1/test",
|
||||
running: false,
|
||||
},
|
||||
{
|
||||
id: "1.21.1 medieval",
|
||||
loader: "Fabric",
|
||||
version: "1.21.1",
|
||||
path: "fabric/1.21.1 medieval",
|
||||
running: false,
|
||||
},
|
||||
].map((server) => (
|
||||
<div
|
||||
{runningServer && (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 rounded-full bg-emerald-500 animate-pulse" />
|
||||
<span className="text-emerald-400 text-xs font-medium">{runningServer.id}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 실행 중인 서버가 있으면 상태 카드 표시 */}
|
||||
{runningServer && (
|
||||
<div className="bg-emerald-500/10 border border-emerald-500/30 rounded-xl p-3 mb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 bg-emerald-500/20 rounded-lg flex items-center justify-center">
|
||||
<div className="w-3 h-3 rounded-full bg-emerald-500 animate-pulse" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-white text-sm font-medium">{runningServer.id}</p>
|
||||
<p className="text-zinc-400 text-xs">
|
||||
<span className={runningServer.loader === 'NeoForge' ? 'text-orange-400' : 'text-blue-400'}>
|
||||
{runningServer.loader}
|
||||
</span>
|
||||
<span className="mx-1">•</span>
|
||||
{runningServer.version}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className="px-4 py-2 text-xs font-medium rounded-lg bg-red-600/20 text-red-400 hover:bg-red-600/30 transition-colors"
|
||||
onClick={() => setToast('서버 종료 기능 준비 중', true)}
|
||||
>
|
||||
종료
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 서버 선택 (접힘/펼침) */}
|
||||
<div className="border border-zinc-800 rounded-xl overflow-hidden">
|
||||
<button
|
||||
onClick={() => setIsServerListExpanded(!isServerListExpanded)}
|
||||
className="w-full flex items-center justify-between p-3 bg-zinc-800/50 hover:bg-zinc-800 transition-colors"
|
||||
>
|
||||
<span className="text-zinc-300 text-sm">
|
||||
{runningServer ? '다른 서버 선택' : '서버 선택'}
|
||||
</span>
|
||||
<ChevronDown
|
||||
size={16}
|
||||
className={`text-zinc-400 transition-transform ${isServerListExpanded ? 'rotate-180' : ''}`}
|
||||
/>
|
||||
</button>
|
||||
|
||||
<AnimatePresence>
|
||||
{isServerListExpanded && (
|
||||
<motion.div
|
||||
initial={{ height: 0, opacity: 0 }}
|
||||
animate={{ height: 'auto', opacity: 1 }}
|
||||
exit={{ height: 0, opacity: 0 }}
|
||||
className="overflow-hidden"
|
||||
>
|
||||
<div className="p-2 space-y-1 max-h-[200px] overflow-y-auto custom-scrollbar">
|
||||
{servers.filter(s => !s.running).map((server) => (
|
||||
<button
|
||||
key={server.path}
|
||||
className={`flex items-center justify-between p-3 rounded-xl transition-colors ${
|
||||
server.running
|
||||
? "bg-emerald-500/10 border border-emerald-500/30"
|
||||
: "bg-zinc-800/50"
|
||||
onClick={() => {
|
||||
setSelectedServer(server.path);
|
||||
}}
|
||||
className={`w-full flex items-center justify-between p-3 rounded-lg transition-colors ${
|
||||
selectedServer === server.path
|
||||
? 'bg-zinc-700 border border-zinc-600'
|
||||
: 'bg-zinc-800/30 hover:bg-zinc-800/50'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<div
|
||||
className={`w-2.5 h-2.5 rounded-full ${
|
||||
server.running
|
||||
? "bg-emerald-500 animate-pulse"
|
||||
: "bg-zinc-600"
|
||||
}`}
|
||||
/>
|
||||
<div>
|
||||
<p className="text-white text-sm font-medium">
|
||||
{server.id}
|
||||
</p>
|
||||
<div className={`w-4 h-4 rounded-full border-2 flex items-center justify-center ${
|
||||
selectedServer === server.path
|
||||
? 'border-emerald-500'
|
||||
: 'border-zinc-600'
|
||||
}`}>
|
||||
{selectedServer === server.path && (
|
||||
<div className="w-2 h-2 rounded-full bg-emerald-500" />
|
||||
)}
|
||||
</div>
|
||||
<div className="text-left">
|
||||
<p className="text-white text-sm">{server.id}</p>
|
||||
<p className="text-zinc-500 text-xs">
|
||||
<span
|
||||
className={`${
|
||||
server.loader === "NeoForge"
|
||||
? "text-orange-400"
|
||||
: "text-blue-400"
|
||||
}`}
|
||||
>
|
||||
<span className={server.loader === 'NeoForge' ? 'text-orange-400' : 'text-blue-400'}>
|
||||
{server.loader}
|
||||
</span>
|
||||
<span className="mx-1">•</span>
|
||||
|
|
@ -1558,28 +1602,28 @@ export default function Admin({ isMobile = false }) {
|
|||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className={`px-3 py-1.5 text-xs font-medium rounded-lg transition-colors ${
|
||||
server.running
|
||||
? "bg-red-600/20 text-red-400 hover:bg-red-600/30"
|
||||
: "bg-emerald-600/20 text-emerald-400 hover:bg-emerald-600/30"
|
||||
}`}
|
||||
onClick={() => {
|
||||
// TODO: 서버 시작/종료 기능 구현
|
||||
setToast(
|
||||
`서버 ${
|
||||
server.running ? "종료" : "시작"
|
||||
} 기능 준비 중`,
|
||||
true
|
||||
);
|
||||
}}
|
||||
>
|
||||
{server.running ? "종료" : "시작"}
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 시작 버튼 */}
|
||||
{selectedServer && !runningServer && (
|
||||
<div className="p-2 pt-0">
|
||||
<button
|
||||
className="w-full py-2.5 bg-emerald-600 hover:bg-emerald-500 text-white text-sm font-medium rounded-lg transition-colors"
|
||||
onClick={() => setToast('서버 시작 기능 준비 중', true)}
|
||||
>
|
||||
선택한 서버 시작
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
|
||||
{/* 서버 성능 모니터링 */}
|
||||
<div className="bg-zinc-900 border border-zinc-800 rounded-2xl p-4">
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue