KyrosDev Claude commited on
Commit
dc6398c
·
1 Parent(s): 5486429

優化認證流程和頁面導航邏輯

Browse files

主要變更:
- 擴充 auth.js 新增統一認證方法 (checkPageAuth, initProtectedPage)
- 統一所有頁面使用 authManager 處理認證
- 修正導航流程:登入 → dashboard → 各功能頁面
- 統一 Home 按鈕導向 dashboard.html
- 移除各頁面重複的認證代碼
- 保持 sessionStorage 和 15分鐘自動登出機制

技術改進:
- 減少代碼重複,提升可維護性
- 統一認證邏輯,避免各頁面獨立實作
- 保持開發模式支援 (localhost 自動跳過認證)

Co-Authored-By: Claude <noreply@anthropic.com>

frontend/dashboard.html CHANGED
@@ -316,78 +316,36 @@
316
  <script src="js/utils.js"></script>
317
  <script src="js/auth.js"></script>
318
  <script>
319
- // Supabase 客戶端
320
  let supabaseLicense = null;
321
  let supabaseVersion = null;
322
- let isDevMode = false;
323
 
324
- // 初始化
325
- async function init() {
326
  try {
327
- // 檢查是否為本地開發環境
328
- isDevMode = window.location.hostname === 'localhost' ||
329
- window.location.hostname === '127.0.0.1' ||
330
- window.location.port === '8000' ||
331
- window.location.port === '8080';
332
-
333
- if (isDevMode) {
334
- console.log('🔧 開發模式 - 跳過認證');
335
-
336
- // 使用統一的用戶資訊更新方式
337
- updateUserInfo('admin@kstools.dev');
338
-
339
-
340
- // 隱藏載入畫面
341
- document.getElementById('loading-overlay').style.display = 'none';
342
- return;
343
- }
344
-
345
- // 生產模式 - 等待配置載入
346
- if (!window.APP_CONFIG.CONFIG_LOADED) {
347
- await new Promise(resolve => {
348
- window.addEventListener('configLoaded', resolve, { once: true });
349
- });
350
- }
351
-
352
- // 初始化雙 Supabase 客戶端
353
- if (window.APP_CONFIG.SUPABASE_LICENSE_URL && window.APP_CONFIG.SUPABASE_LICENSE_ANON_KEY) {
354
- supabaseLicense = supabase.createClient(
355
- window.APP_CONFIG.SUPABASE_LICENSE_URL,
356
- window.APP_CONFIG.SUPABASE_LICENSE_ANON_KEY
357
- );
358
- }
359
-
360
- if (window.APP_CONFIG.SUPABASE_VERSION_URL && window.APP_CONFIG.SUPABASE_VERSION_ANON_KEY) {
361
- supabaseVersion = supabase.createClient(
362
- window.APP_CONFIG.SUPABASE_VERSION_URL,
363
- window.APP_CONFIG.SUPABASE_VERSION_ANON_KEY
364
- );
365
- }
366
-
367
- // 檢查登入狀態
368
- if (!supabaseLicense) {
369
- window.location.href = '/login.html';
370
- return;
371
- }
372
 
373
- const { data: { session } } = await supabaseLicense.auth.getSession();
374
- if (!session) {
375
- window.location.href = '/login.html';
376
- return;
 
 
377
  }
378
 
379
- // 使用統一的用戶資訊更新方式
380
- updateUserInfo(session.user.email);
381
-
382
  // 載入統計資料
383
  await loadDashboardStats();
384
 
385
- // 隱藏載入畫面
386
- document.getElementById('loading-overlay').style.display = 'none';
387
-
388
  } catch (error) {
389
- console.error('初始化失敗:', error);
390
- Utils.showError('初始化失敗,請重新整理頁面');
391
  }
392
  }
393
 
@@ -395,7 +353,7 @@
395
  async function loadDashboardStats() {
396
  try {
397
  // 開發模式使用模擬資料
398
- if (isDevMode) {
399
  const mockLicenseStats = {
400
  total: 25,
401
  active: 18,
@@ -553,43 +511,26 @@
553
  }
554
  }
555
 
556
- // 統一的戶資訊更新
557
- function updateUserInfo(email) {
558
- const userInfo = document.getElementById('userInfo');
559
- if (userInfo) {
560
- userInfo.innerHTML = `
561
- <div class="user-profile">
562
- <span class="user-email">${email}</span>
563
- <button class="btn btn-sm btn-secondary" onclick="logout()">
564
- <i class="fas fa-sign-out-alt"></i>
565
- 登出
566
- </button>
567
- </div>
568
- `;
569
- }
570
- }
571
-
572
- // 登出
573
  async function logout() {
574
- // 開發模式直接跳轉
575
- if (isDevMode) {
576
- console.log('🔧 開發模式 - 模擬登出');
577
- window.location.href = '/login.html';
578
- return;
579
- }
580
-
581
- if (!supabaseLicense) return;
582
-
583
- try {
584
- await supabaseLicense.auth.signOut();
585
- window.location.href = '/login.html';
586
- } catch (error) {
587
- console.error('登出失敗:', error);
588
  }
589
  }
590
 
591
- // 頁面載入時初始化
592
- document.addEventListener('DOMContentLoaded', init);
 
 
 
 
 
 
 
 
 
 
 
593
  </script>
594
  </body>
595
  </html>
 
316
  <script src="js/utils.js"></script>
317
  <script src="js/auth.js"></script>
318
  <script>
319
+ // 使用統一的 AuthManager
320
  let supabaseLicense = null;
321
  let supabaseVersion = null;
 
322
 
323
+ // 初始化儀表板
324
+ async function initDashboard() {
325
  try {
326
+ // 初始化雙 Supabase 客戶端(如果需要)
327
+ if (window.APP_CONFIG?.CONFIG_LOADED) {
328
+ if (window.APP_CONFIG.SUPABASE_LICENSE_URL && window.APP_CONFIG.SUPABASE_LICENSE_ANON_KEY) {
329
+ supabaseLicense = window.supabase?.createClient(
330
+ window.APP_CONFIG.SUPABASE_LICENSE_URL,
331
+ window.APP_CONFIG.SUPABASE_LICENSE_ANON_KEY
332
+ );
333
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
334
 
335
+ if (window.APP_CONFIG.SUPABASE_VERSION_URL && window.APP_CONFIG.SUPABASE_VERSION_ANON_KEY) {
336
+ supabaseVersion = window.supabase?.createClient(
337
+ window.APP_CONFIG.SUPABASE_VERSION_URL,
338
+ window.APP_CONFIG.SUPABASE_VERSION_ANON_KEY
339
+ );
340
+ }
341
  }
342
 
 
 
 
343
  // 載入統計資料
344
  await loadDashboardStats();
345
 
 
 
 
346
  } catch (error) {
347
+ console.error('儀表板初始化失敗:', error);
348
+ Utils.showError('載入計資料失敗');
349
  }
350
  }
351
 
 
353
  async function loadDashboardStats() {
354
  try {
355
  // 開發模式使用模擬資料
356
+ if (window.authManager?.isDevMode) {
357
  const mockLicenseStats = {
358
  total: 25,
359
  active: 18,
 
511
  }
512
  }
513
 
514
+ // 使 authManager 的登出方法
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
515
  async function logout() {
516
+ if (window.authManager) {
517
+ await window.authManager.handleLogout();
 
 
 
 
 
 
 
 
 
 
 
 
518
  }
519
  }
520
 
521
+ // 頁面載入時使用統一認證初始化
522
+ document.addEventListener('DOMContentLoaded', async () => {
523
+ if (window.authManager) {
524
+ await window.authManager.initProtectedPage('dashboard', initDashboard);
525
+ } else {
526
+ // 等待 authManager 載入
527
+ window.addEventListener('supabaseReady', async () => {
528
+ if (window.authManager) {
529
+ await window.authManager.initProtectedPage('dashboard', initDashboard);
530
+ }
531
+ });
532
+ }
533
+ });
534
  </script>
535
  </body>
536
  </html>
frontend/index.html CHANGED
@@ -89,6 +89,12 @@
89
  </div>
90
 
91
  <ul class="nav-menu">
 
 
 
 
 
 
92
  <li class="nav-item active" data-page="dashboard">
93
  <a href="#" class="nav-link">
94
  <i class="fas fa-chart-line"></i>
@@ -148,9 +154,6 @@
148
  <button class="mobile-menu-btn" id="mobileMenuBtn">
149
  <i class="fas fa-bars"></i>
150
  </button>
151
- <button class="btn btn-sm btn-outline" onclick="window.location.href='/dashboard.html'" title="回到主控台">
152
- <i class="fas fa-home"></i>
153
- </button>
154
  <h1 id="pageTitle">系統儀表板</h1>
155
  </div>
156
  <div class="header-right">
@@ -210,72 +213,29 @@
210
  <script>
211
  // 手機選單事件由 app.js 統一處理
212
 
213
- // 簡化初始化
214
- document.addEventListener('DOMContentLoaded', () => {
215
- console.log('🚀 頁面初始化');
216
-
217
- // 檢查是否為開發環境
218
- const isDevMode = window.location.hostname === 'localhost' ||
219
- window.location.hostname === '127.0.0.1';
220
-
221
- if (isDevMode) {
222
- console.log('🔧 開發模式 - 直接啟動應用');
223
- // 開發模式直接啟動
224
- setTimeout(() => {
225
- const loadingOverlay = document.getElementById('loading-overlay');
226
- if (loadingOverlay) {
227
- loadingOverlay.classList.add('hidden');
228
- }
229
-
230
- if (window.app) {
231
- window.app.init().catch(console.error);
232
- }
233
- }, 1000);
234
- return;
235
  }
236
-
237
- // 正式環境才等待 AuthManager 準備好
238
- setTimeout(() => {
239
- const loadingOverlay = document.getElementById('loading-overlay');
240
-
241
- try {
242
- if (window.authManager?.initialized && window.authManager.isAuthenticated()) {
243
- console.log('✅ 用戶已認證,啟動應用');
244
- if (window.app) {
245
- window.app.init().then(() => {
246
- // 應用啟動成功後隱藏載入動畫
247
- if (loadingOverlay) {
248
- loadingOverlay.classList.add('hidden');
249
- }
250
- window.app.restoreSidebarState();
251
- }).catch(error => {
252
- console.error('❌ 應用啟動失敗:', error);
253
- // 即使失敗也要隱藏載入動畫
254
- if (loadingOverlay) {
255
- loadingOverlay.classList.add('hidden');
256
- }
257
- });
258
- } else {
259
- // 沒有 app 對象也要隱藏載入動畫
260
- if (loadingOverlay) {
261
- loadingOverlay.classList.add('hidden');
262
- }
263
- }
264
- } else {
265
- console.log('⚠️ 等待認證或重定向');
266
- // 如果未認證,也要隱藏載入動畫(因為會重定向)
267
- if (loadingOverlay) {
268
- loadingOverlay.classList.add('hidden');
269
- }
270
- }
271
- } catch (error) {
272
- console.error('❌ 初始化過程出錯:', error);
273
- // 出錯時也要隱藏載入動畫
274
- if (loadingOverlay) {
275
- loadingOverlay.classList.add('hidden');
276
  }
277
- }
278
- }, 3000);
279
  });
280
  </script>
281
  </body>
 
89
  </div>
90
 
91
  <ul class="nav-menu">
92
+ <li class="nav-item">
93
+ <a href="/dashboard.html" class="nav-link">
94
+ <i class="fas fa-home"></i>
95
+ <span>返回主控台</span>
96
+ </a>
97
+ </li>
98
  <li class="nav-item active" data-page="dashboard">
99
  <a href="#" class="nav-link">
100
  <i class="fas fa-chart-line"></i>
 
154
  <button class="mobile-menu-btn" id="mobileMenuBtn">
155
  <i class="fas fa-bars"></i>
156
  </button>
 
 
 
157
  <h1 id="pageTitle">系統儀表板</h1>
158
  </div>
159
  <div class="header-right">
 
213
  <script>
214
  // 手機選單事件由 app.js 統一處理
215
 
216
+ // 統一使用 authManager 初始化
217
+ document.addEventListener('DOMContentLoaded', async () => {
218
+ console.log('🚀 授權管理頁面初始化');
219
+
220
+ // 初始化應用的函數
221
+ async function initApp() {
222
+ if (window.app) {
223
+ await window.app.init();
224
+ window.app.restoreSidebarState();
225
+ }
 
 
 
 
 
 
 
 
 
 
 
 
226
  }
227
+
228
+ // 使用 authManager 的統一認證
229
+ if (window.authManager) {
230
+ await window.authManager.initProtectedPage('index', initApp);
231
+ } else {
232
+ // 等待 authManager 載入
233
+ window.addEventListener('supabaseReady', async () => {
234
+ if (window.authManager) {
235
+ await window.authManager.initProtectedPage('index', initApp);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
236
  }
237
+ });
238
+ }
239
  });
240
  </script>
241
  </body>
frontend/js/auth.js CHANGED
@@ -339,10 +339,9 @@ class AuthManager {
339
  }
340
 
341
  redirectToDashboard() {
342
- console.log('🔄 重定向到後台首頁');
343
- if (window.location.pathname.includes('login.html')) {
344
- window.location.href = '/';
345
- }
346
  }
347
 
348
  updateAuthUI() {
@@ -428,6 +427,93 @@ class AuthManager {
428
  });
429
  }
430
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
431
  // Development helper
432
  setSupabaseConfig(url, anonKey) {
433
  Utils.setStorage('supabaseConfig', { url, anonKey });
 
339
  }
340
 
341
  redirectToDashboard() {
342
+ console.log('🔄 重定向到儀表板');
343
+ // 統一導向到 dashboard.html
344
+ window.location.href = '/dashboard.html';
 
345
  }
346
 
347
  updateAuthUI() {
 
427
  });
428
  }
429
 
430
+ // 統一的頁面認證檢查方法
431
+ async checkPageAuth(pageName = 'protected') {
432
+ console.log(`🔐 檢查頁面認證: ${pageName}`);
433
+
434
+ // 開發模式直接通過
435
+ if (this.isDevMode) {
436
+ console.log('🔧 開發模式 - 跳過認證檢查');
437
+ this.updateAuthUI();
438
+ return true;
439
+ }
440
+
441
+ // 等待初始化完成
442
+ if (!this.initialized) {
443
+ console.log('⏳ 等待 AuthManager 初始化...');
444
+ await new Promise(resolve => {
445
+ const checkInit = setInterval(() => {
446
+ if (this.initialized) {
447
+ clearInterval(checkInit);
448
+ resolve();
449
+ }
450
+ }, 100);
451
+ // 最多等待 5 秒
452
+ setTimeout(() => {
453
+ clearInterval(checkInit);
454
+ resolve();
455
+ }, 5000);
456
+ });
457
+ }
458
+
459
+ // 檢查認證狀態
460
+ if (!this.isAuthenticated()) {
461
+ console.log('❌ 用戶未登入,重定向到登入頁');
462
+ this.redirectToLogin();
463
+ return false;
464
+ }
465
+
466
+ console.log('✅ 認證檢查通過');
467
+ this.updateAuthUI();
468
+ this.startActivityMonitoring();
469
+ return true;
470
+ }
471
+
472
+ // 初始化受保護頁面
473
+ async initProtectedPage(pageName, callback) {
474
+ console.log(`🚀 初始化受保護頁面: ${pageName}`);
475
+
476
+ // 先隱藏載入畫面
477
+ const loadingOverlay = document.getElementById('loading-overlay');
478
+
479
+ try {
480
+ // 檢查認證
481
+ const isAuth = await this.checkPageAuth(pageName);
482
+
483
+ if (isAuth) {
484
+ // 認證通過,執行頁面特定的初始化
485
+ if (callback && typeof callback === 'function') {
486
+ await callback();
487
+ }
488
+ }
489
+ } catch (error) {
490
+ console.error('❌ 頁面初始化失敗:', error);
491
+ Utils.showError('頁面載入失敗,請重新整理');
492
+ } finally {
493
+ // 無論成功或失敗都隱藏載入畫面
494
+ if (loadingOverlay) {
495
+ loadingOverlay.style.display = 'none';
496
+ }
497
+ }
498
+ }
499
+
500
+ // 統一的登出方法(增加清理和重定向)
501
+ async handleLogout() {
502
+ try {
503
+ const result = await this.logout();
504
+ if (result.success) {
505
+ Utils.showSuccess('登出成功');
506
+ this.redirectToLogin();
507
+ } else {
508
+ Utils.showError('登出失敗');
509
+ }
510
+ } catch (error) {
511
+ console.error('登出錯誤:', error);
512
+ // 即使出錯也重定向到登入頁
513
+ this.redirectToLogin();
514
+ }
515
+ }
516
+
517
  // Development helper
518
  setSupabaseConfig(url, anonKey) {
519
  Utils.setStorage('supabaseConfig', { url, anonKey });
frontend/versions.html CHANGED
@@ -773,7 +773,7 @@
773
  <button class="mobile-menu-btn" id="mobileMenuBtn">
774
  <i class="fas fa-bars"></i>
775
  </button>
776
- <button class="btn btn-sm btn-outline" onclick="window.location.href='/dashboard.html'" title="回到主控台">
777
  <i class="fas fa-home"></i>
778
  </button>
779
  <h1 id="pageTitle">版本列表</h1>
@@ -835,30 +835,19 @@
835
 
836
  async init() {
837
  console.log('🚀 初始化版本管理應用...');
838
-
839
- // 檢查開發模式
840
- this.isDevMode = window.location.hostname === 'localhost' ||
841
- window.location.hostname === '127.0.0.1';
842
 
843
- if (this.isDevMode) {
844
- console.log('🔧 開發模式啟用');
845
- this.setupEventListeners();
846
- this.updateUserInfo('admin@kstools.dev');
847
- this.loadPageContent('versions');
848
- } else {
849
- // 生產模式初始化
850
- await this.initSupabase();
851
- await this.checkAuth();
852
- }
853
 
854
- // 設定頁面切換
 
855
  this.setupPageNavigation();
856
-
857
- // 隱藏載入
858
- const loadingOverlay = document.getElementById('loading-overlay');
859
- if (loadingOverlay) {
860
- loadingOverlay.style.display = 'none';
861
- }
862
  }
863
 
864
  async initSupabase() {
@@ -883,37 +872,6 @@
883
  }
884
  }
885
 
886
- async checkAuth() {
887
- if (!this.supabaseLicense) {
888
- window.location.href = '/login.html';
889
- return;
890
- }
891
-
892
- const { data: { session } } = await this.supabaseLicense.auth.getSession();
893
- if (!session) {
894
- window.location.href = '/login.html';
895
- return;
896
- }
897
-
898
- this.updateUserInfo(session.user.email);
899
- this.setupEventListeners();
900
- this.loadPageContent('versions');
901
- }
902
-
903
- updateUserInfo(email) {
904
- const userInfo = document.getElementById('userInfo');
905
- if (userInfo) {
906
- userInfo.innerHTML = `
907
- <div class="user-profile">
908
- <span class="user-email">${email}</span>
909
- <button class="btn btn-sm btn-secondary" onclick="versionApp.logout()">
910
- <i class="fas fa-sign-out-alt"></i>
911
- 登出
912
- </button>
913
- </div>
914
- `;
915
- }
916
- }
917
 
918
  setupEventListeners() {
919
  // 手機選單功能
@@ -1642,19 +1600,8 @@
1642
  }
1643
 
1644
  async logout() {
1645
- if (this.isDevMode) {
1646
- console.log('🔧 開發模式 - 模擬登出');
1647
- window.location.href = '/login.html';
1648
- return;
1649
- }
1650
-
1651
- if (this.supabaseLicense) {
1652
- try {
1653
- await this.supabaseLicense.auth.signOut();
1654
- window.location.href = '/login.html';
1655
- } catch (error) {
1656
- console.error('登出失敗:', error);
1657
- }
1658
  }
1659
  }
1660
 
@@ -1768,11 +1715,22 @@
1768
  // 全域實例
1769
  let versionApp;
1770
 
1771
- // 頁面載入時初始化
1772
  document.addEventListener('DOMContentLoaded', async () => {
1773
  console.log('🚀 版本管理系統初始化');
1774
  versionApp = new VersionApp();
1775
- await versionApp.init();
 
 
 
 
 
 
 
 
 
 
 
1776
  });
1777
 
1778
  // 手機選單功能由 VersionApp 統一處理
 
773
  <button class="mobile-menu-btn" id="mobileMenuBtn">
774
  <i class="fas fa-bars"></i>
775
  </button>
776
+ <button class="btn btn-sm btn-outline" onclick="window.location.href='/dashboard.html'" title="回到儀表板">
777
  <i class="fas fa-home"></i>
778
  </button>
779
  <h1 id="pageTitle">版本列表</h1>
 
835
 
836
  async init() {
837
  console.log('🚀 初始化版本管理應用...');
 
 
 
 
838
 
839
+ // 使用 authManager 的開發模式狀態
840
+ this.isDevMode = window.authManager?.isDevMode || false;
841
+
842
+ // 初始化 Supabase
843
+ await this.initSupabase();
 
 
 
 
 
844
 
845
+ // 設定事件監聽和頁面導航
846
+ this.setupEventListeners();
847
  this.setupPageNavigation();
848
+
849
+ // 載入預設頁
850
+ this.loadPageContent('versions');
 
 
 
851
  }
852
 
853
  async initSupabase() {
 
872
  }
873
  }
874
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
875
 
876
  setupEventListeners() {
877
  // 手機選單功能
 
1600
  }
1601
 
1602
  async logout() {
1603
+ if (window.authManager) {
1604
+ await window.authManager.handleLogout();
 
 
 
 
 
 
 
 
 
 
 
1605
  }
1606
  }
1607
 
 
1715
  // 全域實例
1716
  let versionApp;
1717
 
1718
+ // 頁面載入時使用統一認證初始化
1719
  document.addEventListener('DOMContentLoaded', async () => {
1720
  console.log('🚀 版本管理系統初始化');
1721
  versionApp = new VersionApp();
1722
+
1723
+ // 使用 authManager 的統一認證
1724
+ if (window.authManager) {
1725
+ await window.authManager.initProtectedPage('versions', () => versionApp.init());
1726
+ } else {
1727
+ // 等待 authManager 載入
1728
+ window.addEventListener('supabaseReady', async () => {
1729
+ if (window.authManager) {
1730
+ await window.authManager.initProtectedPage('versions', () => versionApp.init());
1731
+ }
1732
+ });
1733
+ }
1734
  });
1735
 
1736
  // 手機選單功能由 VersionApp 統一處理