refactor: app.js를 ES6 모듈로 분리
## 변경사항 - 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>
This commit is contained in:
350
js/dashboard.js
Normal file
350
js/dashboard.js
Normal file
@@ -0,0 +1,350 @@
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user