feat: 텔레그램 사용자용 대시보드 추가

- 텔레그램 환경 감지 시 대시보드 모드 자동 활성화
- 내 서버 목록/관리 (시작/중지/삭제)
- 사용량/비용 통계
- 알림 센터
- Mock 데이터로 UI 테스트 가능

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
kappa
2026-01-22 13:45:25 +09:00
parent abd2320b68
commit ea35848a97
2 changed files with 524 additions and 5 deletions

287
app.js
View File

@@ -66,6 +66,52 @@ const DEPLOY_TIMING = {
COMPLETE: 6000
};
// Mock 데이터 (API 없이 UI 테스트용)
const MOCK_SERVERS = [
{
id: 'srv-001',
name: 'production-web',
region: '🇯🇵 Tokyo',
ip: '45.12.89.101',
os: 'Debian 12',
plan: 'Pro',
status: 'running',
created_at: '2026-01-15',
vcpu: '2 Cores',
ram: '4 GB',
cost: 40700
},
{
id: 'srv-002',
name: 'dev-api',
region: '🇰🇷 Seoul',
ip: '45.12.89.102',
os: 'Ubuntu 24.04',
plan: 'Starter',
status: 'stopped',
created_at: '2026-01-20',
vcpu: '1 Core',
ram: '2 GB',
cost: 17000
}
];
const MOCK_STATS = {
totalCost: 57700,
totalServers: 2,
runningServers: 1,
costBreakdown: [
{ plan: 'Pro', count: 1, cost: 40700 },
{ plan: 'Starter', count: 1, cost: 17000 }
]
};
const MOCK_NOTIFICATIONS = [
{ id: 'n-001', type: 'info', title: '서버 생성 완료', message: 'production-web 서버가 Tokyo 리전에 생성되었습니다.', time: '10분 전', read: false },
{ id: 'n-002', type: 'warning', title: '결제 예정', message: '이번 달 결제가 3일 후 예정되어 있습니다. (₩57,700)', time: '1시간 전', read: false },
{ id: 'n-003', type: 'success', title: '시스템 업데이트 완료', message: 'dev-api 서버의 Ubuntu 패키지가 업데이트되었습니다.', time: '2일 전', read: true }
];
/**
* 가격 포맷팅 (한국 원화)
*/
@@ -84,14 +130,46 @@ function anvilApp() {
wizardStep: 0, // 0: region, 1: plan, 2: os, 3: payment, 4: confirm, 5+: deploying
deployStep: 0,
// 대시보드 상태
dashboardMode: false, // true면 대시보드, false면 랜딩
currentView: 'servers', // 'servers' | 'stats' | 'notifications'
// 텔레그램 연동
telegram: {
isAvailable: false, // 텔레그램 환경인지
user: null, // 사용자 정보
initData: null // 검증용 데이터
},
// 서버 구성
config: {
region: null,
plan: null,
os: null,
payment: null
payment: null,
telegram_id: null
},
// 서버 목록
servers: [],
loadingServers: false,
// 통계
stats: {
totalCost: 0,
totalServers: 0,
runningServers: 0,
costBreakdown: []
},
// 알림
notifications: [],
unreadCount: 0,
// API 상태
apiLoading: false,
apiError: null,
// 대화 메시지
messages: [],
@@ -108,6 +186,188 @@ function anvilApp() {
return ['Micro', 'Starter', 'Pro', 'Business'];
},
// 초기화 (텔레그램 연동 + 대시보드)
init() {
if (window.Telegram?.WebApp) {
const tg = window.Telegram.WebApp;
tg.ready();
tg.expand();
this.telegram.isAvailable = true;
this.telegram.user = tg.initDataUnsafe.user || null;
this.telegram.initData = tg.initData;
// 텔레그램 테마 색상 적용 (선택)
// document.body.style.backgroundColor = tg.backgroundColor;
console.log('[Telegram] Mini App initialized', {
user: this.telegram.user,
platform: tg.platform
});
// 텔레그램 환경이면 대시보드 모드 활성화
// (user가 없어도 텔레그램에서 열린 경우 대시보드 표시)
this.dashboardMode = true;
this.loadDashboard();
console.log('[Telegram] Dashboard mode activated', {
isAvailable: this.telegram.isAvailable,
hasUser: !!this.telegram.user
});
} else {
console.log('[Telegram] Running in web browser mode');
}
},
// 대시보드 초기 로드
async loadDashboard() {
console.log('[Dashboard] Loading dashboard data...');
await Promise.all([
this.fetchServers(),
this.fetchStats(),
this.fetchNotifications()
]);
},
// 서버 목록 조회
async fetchServers() {
this.loadingServers = true;
this.apiError = null;
try {
// TODO: 실제 API 호출로 교체
// const response = await fetch('/api/servers', {
// headers: { 'X-Telegram-Init-Data': this.telegram.initData }
// });
// this.servers = await response.json();
// Mock 데이터 사용
await new Promise(resolve => setTimeout(resolve, 500));
this.servers = [...MOCK_SERVERS];
console.log('[Dashboard] Servers loaded:', this.servers.length);
} catch (error) {
console.error('[Dashboard] Failed to fetch servers:', error);
this.apiError = '서버 목록을 불러오는데 실패했습니다.';
} finally {
this.loadingServers = false;
}
},
// 통계 조회
async fetchStats() {
try {
// TODO: 실제 API 호출로 교체
await new Promise(resolve => setTimeout(resolve, 300));
this.stats = { ...MOCK_STATS };
console.log('[Dashboard] Stats loaded:', this.stats);
} catch (error) {
console.error('[Dashboard] Failed to fetch stats:', error);
}
},
// 알림 조회
async fetchNotifications() {
try {
// TODO: 실제 API 호출로 교체
await new Promise(resolve => setTimeout(resolve, 400));
this.notifications = [...MOCK_NOTIFICATIONS];
this.unreadCount = this.notifications.filter(n => !n.read).length;
console.log('[Dashboard] Notifications loaded:', this.notifications.length);
} catch (error) {
console.error('[Dashboard] Failed to fetch notifications:', error);
}
},
// 서버 시작
async startServer(serverId) {
const server = this.servers.find(s => s.id === serverId);
if (!server) return;
console.log('[Dashboard] Starting server:', serverId);
this.apiLoading = true;
try {
// TODO: 실제 API 호출로 교체
await new Promise(resolve => setTimeout(resolve, 1000));
server.status = 'running';
this.stats.runningServers++;
console.log('[Dashboard] Server started:', serverId);
} catch (error) {
console.error('[Dashboard] Failed to start server:', error);
alert('서버를 시작하는데 실패했습니다.');
} finally {
this.apiLoading = false;
}
},
// 서버 중지
async stopServer(serverId) {
const server = this.servers.find(s => s.id === serverId);
if (!server) return;
console.log('[Dashboard] Stopping server:', serverId);
this.apiLoading = true;
try {
// TODO: 실제 API 호출로 교체
await new Promise(resolve => setTimeout(resolve, 1000));
server.status = 'stopped';
this.stats.runningServers--;
console.log('[Dashboard] Server stopped:', serverId);
} catch (error) {
console.error('[Dashboard] Failed to stop server:', error);
alert('서버를 중지하는데 실패했습니다.');
} finally {
this.apiLoading = false;
}
},
// 서버 삭제
async deleteServer(serverId) {
if (!confirm('정말로 이 서버를 삭제하시겠습니까?')) return;
console.log('[Dashboard] Deleting server:', serverId);
this.apiLoading = true;
try {
// TODO: 실제 API 호출로 교체
await new Promise(resolve => setTimeout(resolve, 800));
const serverIndex = this.servers.findIndex(s => s.id === serverId);
if (serverIndex !== -1) {
const deletedServer = this.servers[serverIndex];
this.servers.splice(serverIndex, 1);
// 통계 업데이트
this.stats.totalServers--;
this.stats.totalCost -= deletedServer.cost;
if (deletedServer.status === 'running') {
this.stats.runningServers--;
}
console.log('[Dashboard] Server deleted:', serverId);
}
} catch (error) {
console.error('[Dashboard] Failed to delete server:', error);
alert('서버를 삭제하는데 실패했습니다.');
} finally {
this.apiLoading = false;
}
},
// 모든 알림 읽음 처리
markAllRead() {
this.notifications.forEach(n => n.read = true);
this.unreadCount = 0;
console.log('[Dashboard] All notifications marked as read');
},
// 뷰 전환
switchView(view) {
this.currentView = view;
console.log('[Dashboard] View switched to:', view);
},
// 가격 조회
getPrice(plan) {
const prices = LAUNCHER_PRICES[plan];
@@ -192,6 +452,11 @@ function anvilApp() {
// 서버 배포 시작
startLaunch() {
// 텔레그램 사용자 ID 추가
if (this.telegram.user) {
this.config.telegram_id = this.telegram.user.id;
}
this.launching = true;
this.wizardStep = 5;
this.deployStep = 1;
@@ -201,6 +466,9 @@ function anvilApp() {
`📦 ${this.config.os} 이미지 준비 중...`
];
// 디버그: config 출력
console.log('[Server Launch] Config:', this.config);
setTimeout(() => {
this.logs.push('✅ 이미지 준비 완료');
this.logs.push('🔧 컨테이너 인스턴스 생성 중...');
@@ -224,13 +492,26 @@ function anvilApp() {
this.launching = false;
this.deployStep = 5;
this.logs.push('🎉 서버가 활성화되었습니다!');
// 대시보드 모드에서는 서버 목록 새로고침
if (this.dashboardMode) {
setTimeout(() => {
this.fetchServers();
this.fetchStats();
}, 1000);
}
}, DEPLOY_TIMING.COMPLETE);
},
// 런처 열기
// 런처 열기 (대시보드용 추가 기능)
openLauncher() {
this.launcherOpen = true;
this.messages = [{ type: 'bot', text: '어느 리전에 서버를 생성할까요?', time: new Date() }];
// 대시보드 모드에서는 서버 생성 후 목록 새로고침
if (this.dashboardMode) {
console.log('[Dashboard] Launcher opened from dashboard');
}
},
// 런처 초기화
@@ -239,7 +520,7 @@ function anvilApp() {
this.launching = false;
this.wizardStep = 0;
this.deployStep = 0;
this.config = { region: null, plan: null, os: null, payment: null };
this.config = { region: null, plan: null, os: null, payment: null, telegram_id: null };
this.messages = [];
this.logs = [];
},