Skip to main content
Glama
+page.svelte8.32 kB
<script> import { onMount } from 'svelte'; import DatabaseTable from '$lib/components/DatabaseTable.svelte'; let selectedTable = 'projects'; let tables = { projects: { name: '프로젝트', data: [], columns: [] }, prds: { name: 'PRDs', data: [], columns: [] }, designs: { name: '설계', data: [], columns: [] }, tasks: { name: '작업', data: [], columns: [] }, tests: { name: '테스트', data: [], columns: [] } }; let loading = false; let error = null; const tableSchemas = { prds: [ { name: 'id', label: 'ID', type: 'text', readonly: true }, { name: 'title', label: '제목', type: 'text' }, { name: 'description', label: '설명', type: 'textarea' }, { name: 'requirements', label: '요구사항', type: 'json-array' }, { name: 'priority', label: '우선순위', type: 'select', options: ['Low', 'Medium', 'High', 'Critical'] }, { name: 'status', label: '상태', type: 'select', options: ['draft', 'active', 'completed', 'cancelled'] }, { name: 'created_at', label: '생성일', type: 'datetime', readonly: true }, { name: 'updated_at', label: '수정일', type: 'datetime', readonly: true } ], tasks: [ { name: 'id', label: 'ID', type: 'text', readonly: true }, { name: 'title', label: '제목', type: 'text' }, { name: 'description', label: '설명', type: 'textarea' }, { name: 'status', label: '상태', type: 'select', options: ['pending', 'in_progress', 'completed', 'cancelled'] }, { name: 'priority', label: '우선순위', type: 'select', options: ['Low', 'Medium', 'High', 'Critical'] }, { name: 'assignee', label: '담당자', type: 'text' }, { name: 'estimatedHours', label: '예상시간', type: 'number' }, { name: 'dueDate', label: '마감일', type: 'date' }, { name: 'createdAt', label: '생성일', type: 'datetime', readonly: true }, { name: 'updatedAt', label: '수정일', type: 'datetime', readonly: true } ], projects: [ { name: 'id', label: 'ID', type: 'text', readonly: true }, { name: 'name', label: '이름', type: 'text' }, { name: 'description', label: '설명', type: 'textarea' }, { name: 'status', label: '상태', type: 'select', options: ['active', 'inactive', 'completed', 'cancelled'] }, { name: 'start_date', label: '시작일', type: 'date' }, { name: 'end_date', label: '종료일', type: 'date' }, { name: 'timeline', label: '타임라인', type: 'json' }, { name: 'created_at', label: '생성일', type: 'datetime', readonly: true }, { name: 'updated_at', label: '수정일', type: 'datetime', readonly: true } ], designs: [ { name: 'id', label: 'ID', type: 'text', readonly: true }, { name: 'title', label: '제목', type: 'text' }, { name: 'description', label: '설명', type: 'textarea' }, { name: 'design_type', label: '설계 유형', type: 'select', options: ['system', 'architecture', 'ui_ux', 'database', 'api'] }, { name: 'status', label: '상태', type: 'select', options: ['draft', 'review', 'approved', 'implemented'] }, { name: 'priority', label: '우선순위', type: 'select', options: ['Low', 'Medium', 'High'] }, { name: 'details', label: '세부사항', type: 'textarea' }, { name: 'requirement_id', label: '요구사항 ID', type: 'text' }, { name: 'created_at', label: '생성일', type: 'datetime', readonly: true }, { name: 'updated_at', label: '수정일', type: 'datetime', readonly: true } ], tests: [ { name: 'id', label: 'ID', type: 'text', readonly: true }, { name: 'title', label: '제목', type: 'text' }, { name: 'description', label: '설명', type: 'textarea' }, { name: 'type', label: '테스트 유형', type: 'select', options: ['unit', 'integration', 'system', 'acceptance', 'regression'] }, { name: 'status', label: '상태', type: 'select', options: ['draft', 'ready', 'active', 'deprecated'] }, { name: 'priority', label: '우선순위', type: 'select', options: ['Low', 'Medium', 'High'] }, { name: 'complexity', label: '복잡도', type: 'select', options: ['Low', 'Medium', 'High'] }, { name: 'automation_status', label: '자동화 상태', type: 'select', options: ['manual', 'automated', 'semi_automated'] }, { name: 'preconditions', label: '전제조건', type: 'textarea' }, { name: 'test_steps', label: '테스트 단계', type: 'textarea' }, { name: 'expected_result', label: '예상결과', type: 'textarea' }, { name: 'task_id', label: '작업 ID', type: 'text' }, { name: 'design_id', label: '설계 ID', type: 'text' }, { name: 'prd_id', label: 'PRD ID', type: 'text' }, { name: 'created_at', label: '생성일', type: 'datetime', readonly: true }, { name: 'updated_at', label: '수정일', type: 'datetime', readonly: true } ] }; onMount(() => { loadTableData(); }); async function loadTableData() { loading = true; error = null; try { // API 호출을 개별적으로 처리하여 오류 방지 const [projectsData, prdsData, designsData, tasksData, testsData] = await Promise.all([ fetch('/api/projects').then(r => r.ok ? r.json() : []).catch(() => []), fetch('/api/prds').then(r => r.ok ? r.json() : []).catch(() => []), fetch('/api/designs').then(r => r.ok ? r.json() : []).catch(() => []), fetch('/api/tasks').then(r => r.ok ? r.json() : []).catch(() => []), fetch('/api/tests').then(r => r.ok ? r.json() : []).catch(() => []) ]); tables.projects.data = projectsData; tables.projects.columns = tableSchemas.projects; tables.prds.data = prdsData; tables.prds.columns = tableSchemas.prds; tables.designs.data = designsData; tables.designs.columns = tableSchemas.designs; tables.tasks.data = tasksData; tables.tasks.columns = tableSchemas.tasks; tables.tests.data = testsData; tables.tests.columns = tableSchemas.tests; } catch (e) { error = 'Error loading data: ' + e.message; } finally { loading = false; } } async function handleSave(event) { const { table, item, isNew } = event.detail; try { const url = isNew ? `/api/${table}` : `/api/${table}/${item.id}`; const method = isNew ? 'POST' : 'PUT'; const response = await fetch(url, { method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(item) }); if (response.ok) { // Reload data to reflect changes await loadTableData(); } else { throw new Error(`Failed to ${isNew ? 'create' : 'update'} ${table} item`); } } catch (e) { error = `Error saving ${table}: ` + e.message; } } async function handleDelete(event) { const { table, id } = event.detail; if (!confirm('정말로 삭제하시겠습니까?')) { return; } try { const response = await fetch(`/api/${table}/${id}`, { method: 'DELETE' }); if (response.ok) { // Reload data to reflect changes await loadTableData(); } else { throw new Error(`Failed to delete ${table} item`); } } catch (e) { error = `Error deleting ${table}: ` + e.message; } } </script> <svelte:head> <title>데이터베이스 관리 - WorkflowMCP Dashboard</title> </svelte:head> <div class="space-y-6"> <div class="flex items-center justify-between"> <h1 class="text-3xl font-bold text-gray-900">데이터베이스 관리</h1> <button on:click={loadTableData} class="btn btn-secondary"> 🔄 새로고침 </button> </div> <!-- Table Selection --> <div class="flex space-x-1 bg-gray-100 p-1 rounded-lg w-fit"> {#each Object.entries(tables) as [key, table]} <button class="px-4 py-2 text-sm rounded-md transition-colors {selectedTable === key ? 'bg-white text-blue-600 shadow-sm' : 'text-gray-600 hover:text-gray-900'}" on:click={() => selectedTable = key} > {table.name} ({table.data.length}) </button> {/each} </div> {#if error} <div class="bg-red-50 border border-red-200 rounded-md p-4"> <div class="text-red-800">{error}</div> </div> {/if} {#if loading} <div class="flex justify-center items-center h-64"> <div class="animate-spin rounded-full h-32 w-32 border-b-2 border-blue-600"></div> </div> {:else} <DatabaseTable tableName={selectedTable} data={tables[selectedTable].data} columns={tables[selectedTable].columns} on:save={handleSave} on:delete={handleDelete} /> {/if} </div>

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/foswmine/workflow-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server