## 변경사항 - app.js (1370줄) → 7개 모듈 (1427줄) - ES6 import/export 문법 사용 - Alpine.js 호환성 유지 (window 전역 노출) ## 모듈 구조 - js/config.js: 상수/설정 (WIZARD_CONFIG, PRICING_DATA, MOCK_*) - js/api.js: ApiService - js/utils.js: formatPrice, switchTab, ping 시뮬레이션 - js/wizard.js: 서버 추천 마법사 로직 - js/pricing.js: 가격표 컴포넌트 - js/dashboard.js: 대시보드 및 텔레그램 연동 - js/app.js: 메인 통합 (모든 모듈 import) ## HTML 변경 - <script type="module" src="js/app.js">로 변경 - 기존 기능 모두 정상 작동 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
351 lines
11 KiB
JavaScript
351 lines
11 KiB
JavaScript
/**
|
|
* Dashboard Component
|
|
* 텔레그램 대시보드 관련 상태 및 메서드
|
|
*/
|
|
|
|
import { MOCK_SERVERS, MOCK_STATS, MOCK_NOTIFICATIONS } from './config.js';
|
|
|
|
/**
|
|
* 대시보드 메서드 생성
|
|
*/
|
|
export function createDashboardMethods() {
|
|
return {
|
|
// 대시보드 상태
|
|
dashboardMode: false, // true면 대시보드, false면 랜딩
|
|
currentView: 'servers', // 'servers' | 'stats' | 'notifications'
|
|
|
|
// 텔레그램 연동
|
|
telegram: {
|
|
isAvailable: false, // 텔레그램 환경인지
|
|
user: null, // 사용자 정보
|
|
initData: null // 검증용 데이터
|
|
},
|
|
|
|
// 웹 로그인 사용자 (텔레그램 로그인 위젯 사용)
|
|
webUser: null,
|
|
|
|
// 현재 로그인된 사용자 (텔레그램 또는 웹)
|
|
get currentUser() {
|
|
return this.telegram.user || this.webUser;
|
|
},
|
|
|
|
// 서버 목록
|
|
servers: [],
|
|
loadingServers: false,
|
|
|
|
// 통계
|
|
stats: {
|
|
totalCost: 0,
|
|
totalServers: 0,
|
|
runningServers: 0,
|
|
costBreakdown: []
|
|
},
|
|
|
|
// 알림
|
|
notifications: [],
|
|
unreadCount: 0,
|
|
|
|
// API 상태
|
|
apiLoading: false,
|
|
apiError: null,
|
|
|
|
// 초기화 (텔레그램 연동 + 대시보드)
|
|
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;
|
|
|
|
console.log('[Telegram] Mini App initialized', {
|
|
user: this.telegram.user,
|
|
platform: tg.platform
|
|
});
|
|
|
|
// 텔레그램 환경이면 대시보드 모드 활성화
|
|
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');
|
|
}
|
|
},
|
|
|
|
// 미니앱 전용 초기화 (/app 페이지용)
|
|
initMiniApp() {
|
|
const tg = window.Telegram?.WebApp;
|
|
|
|
// 실제 텔레그램 환경인지 확인 (initData가 있어야 진짜 텔레그램)
|
|
const isRealTelegram = tg && tg.initData && tg.initData.length > 0;
|
|
|
|
if (isRealTelegram) {
|
|
tg.ready();
|
|
tg.expand();
|
|
|
|
this.telegram.isAvailable = true;
|
|
this.telegram.user = tg.initDataUnsafe.user || null;
|
|
this.telegram.initData = tg.initData;
|
|
|
|
console.log('[MiniApp] Telegram environment detected', {
|
|
user: this.telegram.user,
|
|
platform: tg.platform
|
|
});
|
|
|
|
// 미니앱은 무조건 대시보드 로드
|
|
this.loadDashboard();
|
|
} else {
|
|
console.log('[MiniApp] Not in Telegram environment');
|
|
this.telegram.isAvailable = false;
|
|
|
|
// 웹 브라우저: localStorage에서 webUser 복원
|
|
const savedUser = localStorage.getItem('anvil_web_user');
|
|
if (savedUser) {
|
|
try {
|
|
this.webUser = JSON.parse(savedUser);
|
|
console.log('[MiniApp] Web user restored from localStorage:', this.webUser);
|
|
this.loadDashboard();
|
|
} catch (e) {
|
|
console.error('[MiniApp] Failed to parse saved user:', e);
|
|
localStorage.removeItem('anvil_web_user');
|
|
// 복원 실패시 로그인 위젯 표시
|
|
this.loadTelegramLoginWidget();
|
|
}
|
|
} else {
|
|
// 로그인 필요 - 텔레그램 로그인 위젯 로드
|
|
this.loadTelegramLoginWidget();
|
|
}
|
|
}
|
|
},
|
|
|
|
// 텔레그램 로그인 위젯 동적 로드
|
|
loadTelegramLoginWidget() {
|
|
// Alpine이 DOM을 렌더링할 시간을 주기 위해 약간 지연
|
|
setTimeout(() => {
|
|
const container = document.getElementById('telegram-login-container');
|
|
if (!container) {
|
|
console.log('[MiniApp] Login container not found, retrying...');
|
|
setTimeout(() => this.loadTelegramLoginWidget(), 100);
|
|
return;
|
|
}
|
|
|
|
// 이미 위젯이 있으면 스킵
|
|
if (container.querySelector('iframe')) {
|
|
console.log('[MiniApp] Login widget already loaded');
|
|
return;
|
|
}
|
|
|
|
console.log('[MiniApp] Loading Telegram Login Widget...');
|
|
|
|
// 텔레그램 위젯 스크립트 동적 생성
|
|
const script = document.createElement('script');
|
|
script.src = 'https://telegram.org/js/telegram-widget.js?22';
|
|
script.setAttribute('data-telegram-login', 'AnvilForgeBot');
|
|
script.setAttribute('data-size', 'large');
|
|
script.setAttribute('data-radius', '12');
|
|
script.setAttribute('data-onauth', 'onTelegramAuth(user)');
|
|
script.setAttribute('data-request-access', 'write');
|
|
script.async = true;
|
|
|
|
container.appendChild(script);
|
|
console.log('[MiniApp] Telegram Login Widget script added');
|
|
}, 50);
|
|
},
|
|
|
|
// 웹 텔레그램 로그인 핸들러
|
|
handleWebLogin(user) {
|
|
console.log('[MiniApp] Web login received:', user);
|
|
|
|
// 사용자 정보 저장
|
|
this.webUser = {
|
|
id: user.id,
|
|
first_name: user.first_name,
|
|
last_name: user.last_name || '',
|
|
username: user.username || '',
|
|
photo_url: user.photo_url || '',
|
|
auth_date: user.auth_date
|
|
};
|
|
|
|
// localStorage에 저장 (세션 유지)
|
|
localStorage.setItem('anvil_web_user', JSON.stringify(this.webUser));
|
|
|
|
console.log('[MiniApp] Web user logged in:', this.webUser);
|
|
|
|
// 대시보드 로드
|
|
this.loadDashboard();
|
|
},
|
|
|
|
// 로그아웃
|
|
logout() {
|
|
console.log('[MiniApp] Logging out...');
|
|
|
|
// webUser 초기화
|
|
this.webUser = null;
|
|
localStorage.removeItem('anvil_web_user');
|
|
|
|
// 서버/통계/알림 초기화
|
|
this.servers = [];
|
|
this.stats = { totalCost: 0, totalServers: 0, runningServers: 0, costBreakdown: [] };
|
|
this.notifications = [];
|
|
this.unreadCount = 0;
|
|
|
|
console.log('[MiniApp] Logged out successfully');
|
|
},
|
|
|
|
// 대시보드 초기 로드
|
|
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);
|
|
}
|
|
};
|
|
}
|