서비스 분류별로 구분

This commit is contained in:
caadiq 2025-12-21 18:14:42 +09:00
parent 406d5bf950
commit 3e91702198
4 changed files with 149 additions and 72 deletions

View file

@ -16,12 +16,14 @@ const SERVICES_URLS: &[(&str, &str)] = &[
("jenkins", "http://jenkins:8080/login"),
("portainer", "http://portainer:9000"),
("rustfs", "http://rustfs:9001"),
("uptime-kuma", "http://uptime-kuma:3001"),
("vaultwarden", "http://vaultwarden:80"),
("dozzle", "http://dozzle:8080"),
("it-tools", "http://it-tools:80"),
("ffmpeg-downloader", "http://ffmpeg-gui:3000"),
("cloudbeaver", "http://cloudbeaver:8978"),
("forgejo", "http://forgejo:3000")
("forgejo", "http://forgejo:3000"),
("affine", "http://affine:3010"),
("hoppscotch", "http://hoppscotch:80")
];
/// 시스템 상태 정보 구조체

View file

@ -87,10 +87,6 @@ export default function App() {
</AnimatePresence>
</div>
{/* 푸터 */}
<footer className="py-6 text-center text-slate-500 text-sm mt-auto border-t border-white/5 w-full">
&copy; {new Date().getFullYear()} Caadiq Home Server. All rights reserved.
</footer>
</motion.main>
</div>
);

View file

@ -1,19 +1,29 @@
import {
Terminal,
HeartPulse,
Wrench,
Database,
HardDrive,
Server,
Download,
GitBranch,
FileText,
Zap,
Monitor,
FolderCode,
Sparkles,
KeyRound,
} from "lucide-react";
/**
* 서비스 목록 설정
* 대시보드에 표시될 서비스들을 정의합니다.
* 서비스 카테고리 설정
* 대시보드에 표시될 서비스들을 카테고리별로 그룹화합니다.
*/
export const serviceList = [
export const serviceCategories = [
{
id: "infrastructure",
title: "인프라 관리",
icon: Monitor,
services: [
{
id: "portainer",
title: "Portainer",
@ -28,13 +38,13 @@ export const serviceList = [
icon: Terminal,
url: "https://dozzle.caadiq.co.kr",
},
{
id: "uptime-kuma",
title: "Uptime Kuma",
desc: "서버 상태 모니터링",
icon: HeartPulse,
url: "https://uptime-kuma.caadiq.co.kr",
],
},
{
id: "data-storage",
title: "데이터 & 저장소",
icon: Database,
services: [
{
id: "cloudbeaver",
title: "CloudBeaver",
@ -42,6 +52,27 @@ export const serviceList = [
icon: Database,
url: "https://cloudbeaver.caadiq.co.kr",
},
{
id: "rustfs",
title: "RustFS",
desc: "S3 호환 스토리지",
icon: HardDrive,
url: "https://rustfs.caadiq.co.kr",
},
{
id: "vaultwarden",
title: "Vaultwarden",
desc: "비밀번호 관리",
icon: KeyRound,
url: "https://vaultwarden.caadiq.co.kr",
},
],
},
{
id: "development",
title: "개발 도구",
icon: FolderCode,
services: [
{
id: "forgejo",
title: "Forgejo",
@ -57,12 +88,19 @@ export const serviceList = [
url: "https://jenkins.caadiq.co.kr/login",
},
{
id: "rustfs",
title: "RustFS",
desc: "S3 호환 스토리지",
icon: HardDrive,
url: "https://rustfs.caadiq.co.kr",
id: "hoppscotch",
title: "Hoppscotch",
desc: "API 테스트 도구",
icon: Zap,
url: "https://hoppscotch.caadiq.co.kr",
},
],
},
{
id: "productivity",
title: "생산성 & 유틸리티",
icon: Sparkles,
services: [
{
id: "it-tools",
title: "IT-Tools",
@ -77,4 +115,26 @@ export const serviceList = [
icon: Download,
url: "https://ffmpeg.caadiq.co.kr",
},
{
id: "affine",
title: "AFFiNE",
desc: "문서 및 노트 관리",
icon: FileText,
url: "https://affine.caadiq.co.kr",
},
],
},
];
/**
* 기존 호환성을 위한 평면 서비스 목록
* 백엔드 상태 체크 등에서 사용됩니다.
*/
export const serviceList = serviceCategories.flatMap(
(category) => category.services
);
// 각 카테고리 내 서비스들을 title 기준 알파벳순으로 자동 정렬
serviceCategories.forEach((category) => {
category.services.sort((a, b) => a.title.localeCompare(b.title));
});

View file

@ -1,11 +1,11 @@
import React from 'react';
import { motion } from 'framer-motion';
import ServiceCard from '../components/ServiceCard';
import { serviceList } from '../data/services';
import { serviceCategories } from '../data/services';
/**
* 페이지 컴포넌트
* 사용 가능한 서비스 목록을 표시합니다.
* 서비스 페이지 컴포넌트
* 사용 가능한 서비스 목록을 카테고리별로 그룹화하여 표시합니다.
*
* @param {Object} props
* @param {Object} props.services - 서비스의 상태 (key: service id, value: boolean)
@ -24,11 +24,30 @@ export default function Service({ services }) {
<p className="text-slate-400">운영 중인 모든 서비스를 한눈에 확인하세요.</p>
</div>
{/* 카테고리별 서비스 그룹 */}
<div className="space-y-10">
{serviceCategories.map((category) => (
<div key={category.id}>
{/* 카테고리 헤더 */}
<div className="flex items-center gap-3 mb-5">
<div className="p-2 rounded-lg bg-white/[0.04] border border-white/[0.08]">
<category.icon size={20} className="text-slate-400" />
</div>
<h2 className="text-xl font-semibold text-slate-200">
{category.title}
</h2>
<div className="flex-1 h-px bg-gradient-to-r from-white/[0.08] to-transparent ml-2" />
</div>
{/* 서비스 카드 그리드 */}
<div className="grid grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4 gap-4 sm:gap-6">
{serviceList.map(svc => (
{category.services.map(svc => (
<ServiceCard key={svc.id} {...svc} status={services[svc.id]} />
))}
</div>
</div>
))}
</div>
</motion.section>
);
}