网站首页 网站源码
website
站点相关全部源代码,隐藏了一些关于服务器的信息
@{
    Layout = null;
}

<!DOCTYPE html>

<html lang="zh-Hans">
<head>
    <title>密码迁移管理 - 认证中心</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <link rel="stylesheet" href="@Url.Content("~/css/global.css")" asp-append-version="true"/>
</head>
<body>
    <div class="container wide-container">
        <div class="header">
            <h1>密码迁移管理</h1>
            <p>将旧版本无盐密码迁移到新版本加盐密码,提升系统安全性</p>
        </div>

        @if (TempData["Message"] is string message)
        {
            <div class="success-message">
                @message
            </div>
        }

        @if (TempData["Error"] is string error)
        {
            <div class="error-message">
                @error
            </div>
        }

        <!-- 迁移状态概览 -->
        <div class="section">
            <h2>迁移状态概览</h2>
            <div class="stats-container">
                <div class="stat-item">
                    <div class="stat-number" id="pendingCount">@ViewBag.PendingCount</div>
                    <div class="stat-label">待迁移用户</div>
                </div>
                <div class="stat-item">
                    <div class="stat-number" id="migrationProgress">
                        @if (ViewBag.PendingCount > 0)
                        {
                            <span class="status-pending">进行中</span>
                        }
                        else
                        {
                            <span class="status-completed">已完成</span>
                        }
                    </div>
                    <div class="stat-label">迁移状态</div>
                </div>
            </div>
            
            <button type="button" class="submit-button" onclick="refreshStats()">
                刷新统计
            </button>
        </div>

        <!-- 批量迁移区域 -->
        <div class="section">
            <h2>批量迁移</h2>
            <p>此操作将自动迁移所有使用默认密码(123456)的旧版本用户。自定义密码的用户将在下次登录时自动迁移。</p>
            
            <div class="info-section">
                <div class="info-icon">
                    <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                        <circle cx="12" cy="12" r="10"></circle>
                        <path d="l 12,8 0,4"></path>
                        <path d="l 12,16 0.01,0"></path>
                    </svg>
                </div>
                <div class="info-content">
                    <h3>迁移说明</h3>
                    <ul>
                        <li>只有使用默认密码(123456)的用户会被自动迁移</li>
                        <li>使用自定义密码的用户需要在下次登录时自动迁移</li>
                        <li>迁移后的密码将使用加盐哈希,安全性大幅提升</li>
                        <li>迁移过程是安全的,不会影响用户正常使用</li>
                    </ul>
                </div>
            </div>

            <form method="post" asp-action="BatchMigratePasswords" onsubmit="return confirmMigration()">
                @Html.AntiForgeryToken()
                <button type="submit" class="submit-button" id="migrateBtn">
                    执行批量迁移
                </button>
            </form>
        </div>

        <!-- 单用户检查区域 -->
        <div class="section">
            <h2>单用户检查</h2>
            
            <div class="input-section">
                <label class="input-label" for="userId">用户ID</label>
                <div class="input-wrapper">
                    <input 
                        id="userId"
                        type="text" 
                        class="text-input" 
                        placeholder="请输入用户ID"
                    />
                </div>
            </div>

            <div class="action-buttons">
                <button type="button" class="submit-button" onclick="checkUserMigration()">
                    检查迁移状态
                </button>
            </div>

            <div id="userResult" class="result-section" style="display: none;">
                <!-- 结果将在这里显示 -->
            </div>
        </div>

        <div class="footer">
            <p>密码迁移管理工具 - 仅限系统管理员使用</p>
        </div>
    </div>

    <script src="@Url.Content("~/js/ui-components.js")" asp-append-version="true"></script>
    <script>
        function confirmMigration() {
            const pendingCount = document.getElementById('pendingCount').textContent;
            return confirm(`您确定要执行批量密码迁移吗?\n\n此操作将:\n1. 迁移所有使用默认密码的用户\n2. 提升密码安全性\n3. 当前待迁移用户数:${pendingCount}\n\n建议在系统维护期间执行。`);
        }

        async function refreshStats() {
            try {
                const response = await fetch('/migration-stats');
                const result = await response.json();
                
                if (result.success) {
                    document.getElementById('pendingCount').textContent = result.pendingCount;
                    const progressElement = document.getElementById('migrationProgress');
                    if (result.pendingCount > 0) {
                        progressElement.innerHTML = '<span class="status-pending">进行中</span>';
                    } else {
                        progressElement.innerHTML = '<span class="status-completed">已完成</span>';
                    }
                    showToast('统计信息已刷新', 'success');
                } else {
                    showToast(result.message || '刷新失败', 'error');
                }
            } catch (error) {
                showToast('请求失败: ' + error.message, 'error');
            }
        }

        async function checkUserMigration() {
            const userId = document.getElementById('userId').value;
            if (!userId.trim()) {
                alert('请输入用户ID');
                return;
            }

            try {
                const response = await fetch('/check-user-migration', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/x-www-form-urlencoded',
                        'RequestVerificationToken': document.querySelector('input[name="__RequestVerificationToken"]').value
                    },
                    body: `userId=${encodeURIComponent(userId)}`
                });

                const result = await response.json();
                displayResult(result, '检查结果');
            } catch (error) {
                displayResult({ success: false, message: '请求失败: ' + error.message }, '检查结果');
            }
        }

        function displayResult(result, title) {
            const resultDiv = document.getElementById('userResult');
            const messageClass = result.success ? 'success-message' : 'error-message';
            
            let content = `<h3>${title}</h3>`;
            content += `<div class="${messageClass}">${result.message}</div>`;
            
            if (result.success && result.hasOwnProperty('needsMigration')) {
                const statusClass = result.needsMigration ? 'status-pending' : 'status-completed';
                const statusText = result.needsMigration ? '需要迁移' : '已迁移';
                content += `<p><strong>迁移状态:</strong> <span class="${statusClass}">${statusText}</span></p>`;
            }
            
            resultDiv.innerHTML = content;
            resultDiv.style.display = 'block';
        }
    </script>

    <style>
        .section {
            margin-bottom: 40px;
            padding: 24px;
            background: var(--bg-secondary);
            border-radius: var(--radius);
            border: 1px solid var(--border-color);
        }

        .section h2 {
            color: var(--text-primary);
            margin-bottom: 12px;
            font-size: 20px;
            font-weight: 600;
        }

        .section p {
            color: var(--text-secondary);
            margin-bottom: 20px;
            line-height: 1.5;
        }

        .stats-container {
            display: flex;
            gap: 24px;
            margin-bottom: 20px;
        }

        .stat-item {
            flex: 1;
            text-align: center;
            padding: 20px;
            background: var(--bg-primary);
            border-radius: var(--radius-sm);
            border: 1px solid var(--border-color);
        }

        .stat-number {
            font-size: 32px;
            font-weight: bold;
            color: var(--text-primary);
            margin-bottom: 8px;
        }

        .stat-label {
            font-size: 14px;
            color: var(--text-secondary);
        }

        .status-pending {
            color: #F59E0B;
            font-weight: 600;
        }

        .status-completed {
            color: #10B981;
            font-weight: 600;
        }

        .info-section {
            display: flex;
            align-items: flex-start;
            gap: 16px;
            padding: 16px;
            background: linear-gradient(135deg, #DBEAFE 0%, #BFDBFE 100%);
            color: #1E40AF;
            border: 1px solid #3B82F6;
            border-radius: var(--radius-sm);
            margin-bottom: 20px;
        }

        @@media (prefers-color-scheme: dark) {
            .info-section {
                background: linear-gradient(135deg, #1E3A8A 0%, #1E40AF 100%);
                color: #DBEAFE;
                border-color: #3B82F6;
            }
        }

        .info-icon svg {
            width: 24px;
            height: 24px;
            flex-shrink: 0;
        }

        .info-content h3 {
            margin-bottom: 12px;
            font-size: 16px;
            font-weight: 600;
        }

        .info-content ul {
            margin: 0;
            padding-left: 20px;
        }

        .info-content li {
            margin-bottom: 8px;
            line-height: 1.4;
        }

        .result-section {
            margin-top: 20px;
            padding: 16px;
            background: var(--bg-primary);
            border-radius: var(--radius-sm);
            border: 1px solid var(--border-color);
        }

        .result-section h3 {
            margin-bottom: 12px;
            color: var(--text-primary);
            font-size: 16px;
        }

        @@media (max-width: 768px) {
            .stats-container {
                flex-direction: column;
            }
            
            .info-section {
                flex-direction: column;
            }
        }
    </style>
</body>
</html>
loading