feat: 텔레그램 사용자용 대시보드 추가
- 텔레그램 환경 감지 시 대시보드 모드 자동 활성화 - 내 서버 목록/관리 (시작/중지/삭제) - 사용량/비용 통계 - 알림 센터 - Mock 데이터로 UI 테스트 가능 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
287
app.js
287
app.js
@@ -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 = [];
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user