KyrosDev's picture
優化認證流程和頁面導航邏輯
dc6398c
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>管理中心 - KSTools</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🔑</text></svg>">
<link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
/* Dashboard 專用樣式 */
.dashboard-container {
max-width: 1400px;
margin: 0 auto;
padding: 2rem;
}
/* 頂部導航 */
.top-nav {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
padding-bottom: 1rem;
border-bottom: 1px solid var(--border-color);
}
/* 頁面標題 */
.page-header {
text-align: center;
margin-bottom: 3rem;
}
.page-header h1 {
margin: 0 0 0.5rem 0;
font-size: 2.5rem;
color: var(--text-primary);
}
.page-header p {
margin: 0;
color: var(--text-secondary);
font-size: 1.1rem;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1.5rem;
margin-bottom: 2rem;
}
.stat-card {
background: var(--card-bg);
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 1.5rem;
transition: transform 0.2s, box-shadow 0.2s;
}
.stat-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.stat-card .stat-icon {
width: 48px;
height: 48px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 1rem;
font-size: 1.5rem;
}
.stat-card.licenses .stat-icon {
background: rgba(88, 166, 255, 0.1);
color: var(--accent-blue);
}
.stat-card.versions .stat-icon {
background: rgba(63, 185, 80, 0.1);
color: var(--accent-green);
}
.stat-card.users .stat-icon {
background: rgba(168, 85, 247, 0.1);
color: var(--accent-purple);
}
.stat-card.downloads .stat-icon {
background: rgba(210, 153, 34, 0.1);
color: var(--accent-yellow);
}
.stat-card h3 {
margin: 0;
color: var(--text-secondary);
font-size: 0.875rem;
font-weight: normal;
}
.stat-card .stat-value {
font-size: 2rem;
font-weight: bold;
color: var(--text-primary);
margin: 0.5rem 0;
}
.stat-card .stat-change {
font-size: 0.875rem;
color: var(--text-secondary);
}
.feature-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 1.5rem;
max-width: 1200px;
margin: 0 auto;
}
.feature-card {
background: var(--card-bg);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 1.5rem;
transition: all 0.3s;
cursor: pointer;
text-decoration: none;
color: inherit;
position: relative;
overflow: hidden;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
min-height: 120px;
}
.feature-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(90deg, var(--accent-blue), var(--accent-purple));
transform: scaleX(0);
transition: transform 0.3s;
}
.feature-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
border-color: var(--accent-blue);
}
.feature-card:hover::before {
transform: scaleX(1);
}
.feature-card .feature-icon {
width: 64px;
height: 64px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 1.5rem;
font-size: 2rem;
}
.feature-card.license-feature .feature-icon {
background: linear-gradient(135deg, #58a6ff, #1f6feb);
color: white;
}
.feature-card.version-feature .feature-icon {
background: linear-gradient(135deg, #3fb950, #238636);
color: white;
}
.feature-card {
background: rgba(255, 255, 255, 0.02);
border: 1px solid var(--border-color);
}
.feature-card h2 {
margin: 0;
font-size: 1.1rem;
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
}
.feature-card h2 i {
font-size: 2rem;
color: var(--accent-blue);
}
.feature-card.license-feature h2 i {
color: var(--accent-blue);
}
.feature-card.version-feature h2 i {
color: var(--accent-green);
}
.loading-skeleton {
background: linear-gradient(90deg, var(--card-bg) 25%, var(--border-color) 50%, var(--card-bg) 75%);
background-size: 200% 100%;
animation: loading 1.5s infinite;
border-radius: 4px;
height: 20px;
}
@keyframes loading {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
/* 響應式設計 */
@media (max-width: 768px) {
.dashboard-container {
padding: 1rem;
}
.top-nav {
flex-direction: column;
gap: 1rem;
align-items: stretch;
}
.nav-left, .nav-right {
display: flex;
justify-content: center;
}
.page-header h1 {
font-size: 2rem;
}
.feature-grid {
grid-template-columns: repeat(2, 1fr);
gap: 1rem;
}
.feature-card {
min-height: 100px;
padding: 1rem;
}
.feature-card h2 {
font-size: 1rem;
}
.feature-card h2 i {
font-size: 1.5rem;
}
}
</style>
</head>
<body>
<!-- 載入中畫面 -->
<div id="loading-overlay" class="loading-overlay">
<div class="spinner"></div>
<p>載入中...</p>
</div>
<!-- 主要內容 -->
<div class="dashboard-container">
<!-- 頂部導航 -->
<div class="top-nav">
<div class="nav-left">
</div>
<div class="nav-right">
<div class="header-actions">
<div id="userInfo" class="user-info">
<!-- User info will be populated by JavaScript -->
</div>
</div>
</div>
</div>
<!-- 主標題 -->
<div class="page-header">
<h1>KSTools 管理中心</h1>
<p>授權與版本管理系統</p>
</div>
<!-- 功能卡片 -->
<div class="feature-grid">
<!-- 授權管理 -->
<a href="/index.html" class="feature-card license-feature">
<h2><i class="fas fa-shield-alt"></i> 授權管理</h2>
</a>
<!-- 版本管理 -->
<a href="/versions.html" class="feature-card version-feature">
<h2><i class="fas fa-rocket"></i> 版本管理</h2>
</a>
</div>
</div>
<!-- JavaScript -->
<script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2"></script>
<script src="config.js"></script>
<script src="js/utils.js"></script>
<script src="js/auth.js"></script>
<script>
// 使用統一的 AuthManager
let supabaseLicense = null;
let supabaseVersion = null;
// 初始化儀表板
async function initDashboard() {
try {
// 初始化雙 Supabase 客戶端(如果需要)
if (window.APP_CONFIG?.CONFIG_LOADED) {
if (window.APP_CONFIG.SUPABASE_LICENSE_URL && window.APP_CONFIG.SUPABASE_LICENSE_ANON_KEY) {
supabaseLicense = window.supabase?.createClient(
window.APP_CONFIG.SUPABASE_LICENSE_URL,
window.APP_CONFIG.SUPABASE_LICENSE_ANON_KEY
);
}
if (window.APP_CONFIG.SUPABASE_VERSION_URL && window.APP_CONFIG.SUPABASE_VERSION_ANON_KEY) {
supabaseVersion = window.supabase?.createClient(
window.APP_CONFIG.SUPABASE_VERSION_URL,
window.APP_CONFIG.SUPABASE_VERSION_ANON_KEY
);
}
}
// 載入統計資料
await loadDashboardStats();
} catch (error) {
console.error('儀表板初始化失敗:', error);
Utils.showError('載入統計資料失敗');
}
}
// 載入儀表板統計
async function loadDashboardStats() {
try {
// 開發模式使用模擬資料
if (window.authManager?.isDevMode) {
const mockLicenseStats = {
total: 25,
active: 18,
expiring: 3,
recentActive: 15
};
const mockVersionStats = {
totalVersions: 5,
latestVersion: {
version: '1.2.0',
release_date: new Date().toISOString()
},
totalDownloads: 156,
monthlyDownloads: 42
};
updateStatsDisplay(mockLicenseStats, mockVersionStats);
return;
}
// 生產模式 - 同時載入授權和版本統計
const [licenseStats, versionStats] = await Promise.all([
loadLicenseStats(),
loadVersionStats()
]);
// 更新統計卡片
updateStatsDisplay(licenseStats, versionStats);
} catch (error) {
console.error('載入統計失敗:', error);
}
}
// 載入授權統計
async function loadLicenseStats() {
if (!supabaseLicense) return null;
try {
const { data: licenses, error } = await supabaseLicense
.from('licenses')
.select('*');
if (error) throw error;
const now = new Date();
const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
const sevenDaysLater = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000);
const stats = {
total: licenses.length,
active: licenses.filter(l => l.status === 'active').length,
expiring: licenses.filter(l => {
if (l.status !== 'active' || !l.expires_at) return false;
const expiryDate = new Date(l.expires_at);
return expiryDate <= sevenDaysLater && expiryDate >= now;
}).length,
recentActive: licenses.filter(l => {
if (!l.last_used_at) return false;
return new Date(l.last_used_at) >= thirtyDaysAgo;
}).length
};
return stats;
} catch (error) {
console.error('載入授權統計失敗:', error);
return null;
}
}
// 載入版本統計
async function loadVersionStats() {
if (!supabaseVersion) return null;
try {
// 取得版本列表
const { data: versions, error: versionError } = await supabaseVersion
.from('versions')
.select('*')
.eq('is_active', true)
.order('release_date', { ascending: false });
if (versionError) throw versionError;
// 取得下載記錄
const { data: downloads, error: downloadError } = await supabaseVersion
.from('download_logs')
.select('*');
if (downloadError) throw downloadError;
const now = new Date();
const thisMonth = new Date(now.getFullYear(), now.getMonth(), 1);
const stats = {
totalVersions: versions.length,
latestVersion: versions[0] || null,
totalDownloads: downloads.filter(d => d.action === 'download').length,
monthlyDownloads: downloads.filter(d => {
return d.action === 'download' && new Date(d.downloaded_at) >= thisMonth;
}).length
};
return stats;
} catch (error) {
console.error('載入版本統計失敗:', error);
return null;
}
}
// 更新統計顯示
function updateStatsDisplay(licenseStats, versionStats) {
// 授權統計
if (licenseStats) {
const totalLicensesEl = document.getElementById('totalLicenses');
const activeLicensesEl = document.getElementById('activeLicenses');
const activeUsersEl = document.getElementById('activeUsers');
const featureActiveLicensesEl = document.getElementById('featureActiveLicenses');
const featureExpiringLicensesEl = document.getElementById('featureExpiringLicenses');
if (totalLicensesEl) totalLicensesEl.textContent = licenseStats.total;
if (activeLicensesEl) activeLicensesEl.textContent = `${licenseStats.active} 個有效`;
if (activeUsersEl) activeUsersEl.textContent = licenseStats.recentActive;
if (featureActiveLicensesEl) featureActiveLicensesEl.textContent = licenseStats.active;
if (featureExpiringLicensesEl) featureExpiringLicensesEl.textContent = licenseStats.expiring;
}
// 版本統計
if (versionStats) {
const latestVersionEl = document.getElementById('latestVersion');
const versionDateEl = document.getElementById('versionDate');
const totalDownloadsEl = document.getElementById('totalDownloads');
const monthlyDownloadsEl = document.getElementById('monthlyDownloads');
const featureTotalVersionsEl = document.getElementById('featureTotalVersions');
const featureMonthlyDownloadsEl = document.getElementById('featureMonthlyDownloads');
if (versionStats.latestVersion) {
if (latestVersionEl) latestVersionEl.textContent = versionStats.latestVersion.version;
if (versionDateEl) {
const releaseDate = new Date(versionStats.latestVersion.release_date);
versionDateEl.textContent = releaseDate.toLocaleDateString('zh-TW');
}
} else {
if (latestVersionEl) latestVersionEl.textContent = '無';
if (versionDateEl) versionDateEl.textContent = '--';
}
if (totalDownloadsEl) totalDownloadsEl.textContent = versionStats.totalDownloads;
if (monthlyDownloadsEl) monthlyDownloadsEl.textContent = `本月 ${versionStats.monthlyDownloads}`;
if (featureTotalVersionsEl) featureTotalVersionsEl.textContent = versionStats.totalVersions;
if (featureMonthlyDownloadsEl) featureMonthlyDownloadsEl.textContent = versionStats.monthlyDownloads;
}
}
// 使用 authManager 的登出方法
async function logout() {
if (window.authManager) {
await window.authManager.handleLogout();
}
}
// 頁面載入時使用統一認證初始化
document.addEventListener('DOMContentLoaded', async () => {
if (window.authManager) {
await window.authManager.initProtectedPage('dashboard', initDashboard);
} else {
// 等待 authManager 載入
window.addEventListener('supabaseReady', async () => {
if (window.authManager) {
await window.authManager.initProtectedPage('dashboard', initDashboard);
}
});
}
});
</script>
</body>
</html>