feat(ui): 添加图片放大功能并优化仪表盘布局
实现二维码图片点击放大功能,重构仪表盘顶部布局将联系信息与统计卡片并排显示 添加多语言图片切换功能,根据语言显示不同的赞助和联系方式图片 优化Kiro OAuth流程,增加自动关联凭据到Pools的功能
This commit is contained in:
parent
abf874b43c
commit
0816de2ba2
10 changed files with 448 additions and 76 deletions
|
|
@ -6,6 +6,8 @@ import os from 'os';
|
|||
import crypto from 'crypto';
|
||||
import open from 'open';
|
||||
import { broadcastEvent } from './ui-manager.js';
|
||||
import { autoLinkProviderConfigs } from './service-manager.js';
|
||||
import { CONFIG } from './config-manager.js';
|
||||
|
||||
/**
|
||||
* OAuth 提供商配置
|
||||
|
|
@ -197,6 +199,9 @@ async function createOAuthCallbackServer(config, redirectUri, authClient, credPa
|
|||
timestamp: new Date().toISOString()
|
||||
});
|
||||
|
||||
// 自动关联新生成的凭据到 Pools
|
||||
await autoLinkProviderConfigs(CONFIG);
|
||||
|
||||
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
||||
res.end(generateResponsePage(true, '您可以关闭此页面'));
|
||||
} catch (tokenError) {
|
||||
|
|
@ -439,6 +444,9 @@ async function pollQwenToken(deviceCode, codeVerifier, interval = 5, expiresIn =
|
|||
timestamp: new Date().toISOString()
|
||||
});
|
||||
|
||||
// 自动关联新生成的凭据到 Pools
|
||||
await autoLinkProviderConfigs(CONFIG);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
|
|
@ -519,7 +527,7 @@ export async function handleQwenOAuth(currentConfig, options = {}) {
|
|||
}
|
||||
|
||||
// 启动后台轮询获取令牌
|
||||
const interval = deviceAuth.interval || 5;
|
||||
const interval = 5;
|
||||
// const expiresIn = deviceAuth.expires_in || 1800;
|
||||
const expiresIn = 300;
|
||||
|
||||
|
|
@ -630,6 +638,13 @@ async function handleKiroSocialAuth(provider, currentConfig, options = {}) {
|
|||
* Kiro Builder ID - Device Code Flow(类似 Qwen OAuth 模式)
|
||||
*/
|
||||
async function handleKiroBuilderIDDeviceCode(currentConfig, options = {}) {
|
||||
// 停止之前的轮询任务
|
||||
for (const [existingTaskId] of activeKiroPollingTasks.entries()) {
|
||||
if (existingTaskId.startsWith('kiro-')) {
|
||||
stopKiroPollingTask(existingTaskId);
|
||||
}
|
||||
}
|
||||
|
||||
// 1. 注册 OIDC 客户端
|
||||
const regResponse = await fetch(`${KIRO_OAUTH_CONFIG.ssoOIDCEndpoint}/client/register`, {
|
||||
method: 'POST',
|
||||
|
|
@ -673,22 +688,16 @@ async function handleKiroBuilderIDDeviceCode(currentConfig, options = {}) {
|
|||
|
||||
// 3. 启动后台轮询(类似 Qwen OAuth 的模式)
|
||||
const taskId = `kiro-${deviceAuth.deviceCode.substring(0, 8)}-${Date.now()}`;
|
||||
|
||||
// 停止之前的轮询任务
|
||||
for (const [existingTaskId] of activeKiroPollingTasks.entries()) {
|
||||
if (existingTaskId.startsWith('kiro-')) {
|
||||
stopKiroPollingTask(existingTaskId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 异步轮询
|
||||
pollKiroBuilderIDToken(
|
||||
regData.clientId,
|
||||
regData.clientSecret,
|
||||
deviceAuth.deviceCode,
|
||||
deviceAuth.interval || 5,
|
||||
deviceAuth.expiresIn || 300,
|
||||
taskId,
|
||||
regData.clientId,
|
||||
regData.clientSecret,
|
||||
deviceAuth.deviceCode,
|
||||
5,
|
||||
300,
|
||||
taskId,
|
||||
options
|
||||
).catch(error => {
|
||||
console.error(`${KIRO_OAUTH_CONFIG.logPrefix} 轮询失败 [${taskId}]:`, error);
|
||||
|
|
@ -761,10 +770,11 @@ async function pollKiroBuilderIDToken(clientId, clientSecret, deviceCode, interv
|
|||
|
||||
// 保存令牌(符合现有规范)
|
||||
if (options.saveToConfigs) {
|
||||
const targetDir = path.join(process.cwd(), 'configs', 'kiro');
|
||||
await fs.promises.mkdir(targetDir, { recursive: true });
|
||||
const timestamp = Date.now();
|
||||
credPath = path.join(targetDir, `${timestamp}_oauth_creds.json`);
|
||||
const folderName = `${timestamp}_kiro-auth-token`;
|
||||
const targetDir = path.join(process.cwd(), 'configs', 'kiro', folderName);
|
||||
await fs.promises.mkdir(targetDir, { recursive: true });
|
||||
credPath = path.join(targetDir, `${folderName}.json`);
|
||||
}
|
||||
|
||||
const tokenData = {
|
||||
|
|
@ -790,6 +800,9 @@ async function pollKiroBuilderIDToken(clientId, clientSecret, deviceCode, interv
|
|||
timestamp: new Date().toISOString()
|
||||
});
|
||||
|
||||
// 自动关联新生成的凭据到 Pools
|
||||
await autoLinkProviderConfigs(CONFIG);
|
||||
|
||||
return tokenData;
|
||||
}
|
||||
|
||||
|
|
@ -837,17 +850,17 @@ async function startKiroCallbackServer(codeVerifier, expectedState, options = {}
|
|||
const portEnd = KIRO_OAUTH_CONFIG.callbackPortEnd;
|
||||
|
||||
for (let port = portStart; port <= portEnd; port++) {
|
||||
// 关闭已存在的服务器
|
||||
await closeKiroServer(port);
|
||||
|
||||
try {
|
||||
const server = await createKiroHttpCallbackServer(port, codeVerifier, expectedState, options);
|
||||
activeKiroServers.set(port, server);
|
||||
console.log(`${KIRO_OAUTH_CONFIG.logPrefix} 回调服务器已启动于端口 ${port}`);
|
||||
return port;
|
||||
} catch (err) {
|
||||
// 关闭已存在的服务器
|
||||
await closeKiroServer(port);
|
||||
|
||||
try {
|
||||
const server = await createKiroHttpCallbackServer(port, codeVerifier, expectedState, options);
|
||||
activeKiroServers.set(port, server);
|
||||
console.log(`${KIRO_OAUTH_CONFIG.logPrefix} 回调服务器已启动于端口 ${port}`);
|
||||
return port;
|
||||
} catch (err) {
|
||||
console.log(`${KIRO_OAUTH_CONFIG.logPrefix} 端口 ${port} 被占用,尝试下一个...`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('所有端口都被占用');
|
||||
|
|
@ -925,10 +938,11 @@ function createKiroHttpCallbackServer(port, codeVerifier, expectedState, options
|
|||
let credPath = path.join(os.homedir(), KIRO_OAUTH_CONFIG.credentialsDir, KIRO_OAUTH_CONFIG.credentialsFile);
|
||||
|
||||
if (options.saveToConfigs) {
|
||||
const targetDir = path.join(process.cwd(), 'configs', 'kiro');
|
||||
await fs.promises.mkdir(targetDir, { recursive: true });
|
||||
const timestamp = Date.now();
|
||||
credPath = path.join(targetDir, `${timestamp}_oauth_creds.json`);
|
||||
const folderName = `${timestamp}_kiro-auth-token`;
|
||||
const targetDir = path.join(process.cwd(), 'configs', 'kiro', folderName);
|
||||
await fs.promises.mkdir(targetDir, { recursive: true });
|
||||
credPath = path.join(targetDir, `${folderName}.json`);
|
||||
}
|
||||
|
||||
const saveData = {
|
||||
|
|
@ -953,6 +967,9 @@ function createKiroHttpCallbackServer(port, codeVerifier, expectedState, options
|
|||
timestamp: new Date().toISOString()
|
||||
});
|
||||
|
||||
// 自动关联新生成的凭据到 Pools
|
||||
await autoLinkProviderConfigs(CONFIG);
|
||||
|
||||
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
||||
res.end(generateResponsePage(true, '授权成功!您可以关闭此页面'));
|
||||
|
||||
|
|
|
|||
|
|
@ -71,6 +71,10 @@ import {
|
|||
refreshUsage
|
||||
} from './usage-manager.js';
|
||||
|
||||
import {
|
||||
initImageZoom
|
||||
} from './image-zoom.js';
|
||||
|
||||
/**
|
||||
* 加载初始数据
|
||||
*/
|
||||
|
|
@ -105,6 +109,7 @@ function initApp() {
|
|||
initRoutingExamples(); // 初始化路径路由示例功能
|
||||
initUploadConfigManager(); // 初始化上传配置管理功能
|
||||
initUsageManager(); // 初始化用量管理功能
|
||||
initImageZoom(); // 初始化图片放大功能
|
||||
loadInitialData();
|
||||
|
||||
// 显示欢迎消息
|
||||
|
|
|
|||
|
|
@ -190,6 +190,77 @@ async function handleGenerateCreds(event) {
|
|||
const providerType = button.getAttribute('data-provider');
|
||||
const targetInputId = button.getAttribute('data-target');
|
||||
|
||||
try {
|
||||
// 如果是 Kiro OAuth,先显示认证方式选择对话框
|
||||
if (providerType === 'claude-kiro-oauth') {
|
||||
const modal = document.createElement('div');
|
||||
modal.className = 'modal-overlay';
|
||||
modal.style.display = 'flex';
|
||||
|
||||
modal.innerHTML = `
|
||||
<div class="modal-content" style="max-width: 500px;">
|
||||
<div class="modal-header">
|
||||
<h3><i class="fas fa-key"></i> <span data-i18n="oauth.kiro.selectMethod">${t('oauth.kiro.selectMethod')}</span></h3>
|
||||
<button class="modal-close">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="auth-method-options" style="display: flex; flex-direction: column; gap: 12px;">
|
||||
<!--<button class="auth-method-btn" data-method="google" style="display: flex; align-items: center; gap: 12px; padding: 16px; border: 2px solid #e0e0e0; border-radius: 8px; background: white; cursor: pointer; transition: all 0.2s;">
|
||||
<i class="fab fa-google" style="font-size: 24px; color: #4285f4;"></i>
|
||||
<div style="text-align: left;">
|
||||
<div style="font-weight: 600; color: #333;" data-i18n="oauth.kiro.google">${t('oauth.kiro.google')}</div>
|
||||
<div style="font-size: 12px; color: #666;" data-i18n="oauth.kiro.googleDesc">${t('oauth.kiro.googleDesc')}</div>
|
||||
</div>
|
||||
</button>
|
||||
<button class="auth-method-btn" data-method="github" style="display: flex; align-items: center; gap: 12px; padding: 16px; border: 2px solid #e0e0e0; border-radius: 8px; background: white; cursor: pointer; transition: all 0.2s;">
|
||||
<i class="fab fa-github" style="font-size: 24px; color: #333;"></i>
|
||||
<div style="text-align: left;">
|
||||
<div style="font-weight: 600; color: #333;" data-i18n="oauth.kiro.github">${t('oauth.kiro.github')}</div>
|
||||
<div style="font-size: 12px; color: #666;" data-i18n="oauth.kiro.githubDesc">${t('oauth.kiro.githubDesc')}</div>
|
||||
</div>
|
||||
</button> -->
|
||||
<button class="auth-method-btn" data-method="builder-id" style="display: flex; align-items: center; gap: 12px; padding: 16px; border: 2px solid #e0e0e0; border-radius: 8px; background: white; cursor: pointer; transition: all 0.2s;">
|
||||
<i class="fab fa-aws" style="font-size: 24px; color: #ff9900;"></i>
|
||||
<div style="text-align: left;">
|
||||
<div style="font-weight: 600; color: #333;" data-i18n="oauth.kiro.awsBuilder">${t('oauth.kiro.awsBuilder')}</div>
|
||||
<div style="font-size: 12px; color: #666;" data-i18n="oauth.kiro.awsBuilderDesc">${t('oauth.kiro.awsBuilderDesc')}</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="modal-cancel" data-i18n="modal.provider.cancel">${t('modal.provider.cancel')}</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(modal);
|
||||
|
||||
const closeModal = () => modal.remove();
|
||||
modal.querySelector('.modal-close').onclick = closeModal;
|
||||
modal.querySelector('.modal-cancel').onclick = closeModal;
|
||||
|
||||
modal.querySelectorAll('.auth-method-btn').forEach(btn => {
|
||||
btn.onclick = async () => {
|
||||
const method = btn.dataset.method;
|
||||
closeModal();
|
||||
await proceedWithAuth(providerType, targetInputId, { method });
|
||||
};
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await proceedWithAuth(providerType, targetInputId, {});
|
||||
} catch (error) {
|
||||
console.error('生成凭据失败:', error);
|
||||
showToast(t('common.error'), t('modal.provider.auth.failed') + `: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 实际执行授权逻辑
|
||||
*/
|
||||
async function proceedWithAuth(providerType, targetInputId, extraOptions = {}) {
|
||||
try {
|
||||
showToast(t('common.info'), t('modal.provider.auth.initializing'), 'info');
|
||||
|
||||
|
|
@ -200,7 +271,8 @@ async function handleGenerateCreds(event) {
|
|||
`/providers/${encodeURIComponent(providerType)}/generate-auth-url`,
|
||||
{
|
||||
saveToConfigs: true,
|
||||
providerDir: providerDir
|
||||
providerDir: providerDir,
|
||||
...extraOptions
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -50,8 +50,12 @@ const translations = {
|
|||
'dashboard.contact.title': '联系与赞助',
|
||||
'dashboard.contact.wechat': '扫码进群,注明来意',
|
||||
'dashboard.contact.wechatDesc': '添加微信获取更多技术支持和交流',
|
||||
'dashboard.contact.x': '关注 X.com',
|
||||
'dashboard.contact.xDesc': '在 X 上关注我们获取最新动态',
|
||||
'dashboard.contact.sponsor': '扫码赞助',
|
||||
'dashboard.contact.sponsorDesc': '您的赞助是项目持续发展的动力',
|
||||
'dashboard.contact.coffee': 'Buy me a coffee',
|
||||
'dashboard.contact.coffeeDesc': 'If you like this project, buy me a coffee!',
|
||||
|
||||
// OAuth
|
||||
'oauth.modal.title': 'OAuth 授权',
|
||||
|
|
@ -76,6 +80,18 @@ const translations = {
|
|||
'oauth.processing': '正在完成授权...',
|
||||
'oauth.invalid.url': '该 URL 似乎不包含有效的授权代码',
|
||||
'oauth.error.format': '无效的 URL 格式',
|
||||
'oauth.kiro.selectMethod': '选择认证方式',
|
||||
'oauth.kiro.google': 'Google 账号登录',
|
||||
'oauth.kiro.googleDesc': '使用 Google 账号进行社交登录',
|
||||
'oauth.kiro.github': 'GitHub 账号登录',
|
||||
'oauth.kiro.githubDesc': '使用 GitHub 账号进行社交登录',
|
||||
'oauth.kiro.awsBuilder': 'AWS Builder ID',
|
||||
'oauth.kiro.awsBuilderDesc': '使用 AWS Builder ID 进行设备码授权',
|
||||
'oauth.kiro.authMethodLabel': '认证方式:',
|
||||
'oauth.kiro.step1': '点击下方按钮在浏览器中打开授权链接',
|
||||
'oauth.kiro.step2': '使用您的 {method} 账号登录',
|
||||
'oauth.kiro.step3': '授权完成后页面会自动关闭',
|
||||
'oauth.kiro.step4': '刷新本页面查看凭据文件',
|
||||
|
||||
// Config
|
||||
'config.title': '配置管理',
|
||||
|
|
@ -396,11 +412,15 @@ const translations = {
|
|||
'dashboard.routing.nodeName.kiro': 'Claude Kiro OAuth',
|
||||
'dashboard.routing.nodeName.openai': 'OpenAI Custom',
|
||||
'dashboard.routing.nodeName.qwen': 'Qwen OAuth',
|
||||
'dashboard.contact.title': 'Contact & Sponsor',
|
||||
'dashboard.contact.title': 'Contact & Support',
|
||||
'dashboard.contact.wechat': 'Scan to Join Group',
|
||||
'dashboard.contact.wechatDesc': 'Add WeChat for more technical support and communication',
|
||||
'dashboard.contact.sponsor': 'Scan to Sponsor',
|
||||
'dashboard.contact.sponsorDesc': 'Your sponsorship is the driving force for the project\'s continuous development',
|
||||
'dashboard.contact.x': 'Follow on X.com',
|
||||
'dashboard.contact.xDesc': 'Follow us on X for latest updates',
|
||||
'dashboard.contact.sponsor': 'Scan to Support',
|
||||
'dashboard.contact.sponsorDesc': 'Your support is the driving force for the project\'s continuous development',
|
||||
'dashboard.contact.coffee': 'Buy me a coffee',
|
||||
'dashboard.contact.coffeeDesc': 'If you like this project, buy me a coffee!',
|
||||
|
||||
// OAuth
|
||||
'oauth.modal.title': 'OAuth Authorization',
|
||||
|
|
@ -425,6 +445,18 @@ const translations = {
|
|||
'oauth.processing': 'Completing authorization...',
|
||||
'oauth.invalid.url': 'This URL does not seem to contain a valid auth code',
|
||||
'oauth.error.format': 'Invalid URL format',
|
||||
'oauth.kiro.selectMethod': 'Select Authentication Method',
|
||||
'oauth.kiro.google': 'Google Account Login',
|
||||
'oauth.kiro.googleDesc': 'Login with Google account',
|
||||
'oauth.kiro.github': 'GitHub Account Login',
|
||||
'oauth.kiro.githubDesc': 'Login with GitHub account',
|
||||
'oauth.kiro.awsBuilder': 'AWS Builder ID',
|
||||
'oauth.kiro.awsBuilderDesc': 'Device code authorization via AWS Builder ID',
|
||||
'oauth.kiro.authMethodLabel': 'Auth Method:',
|
||||
'oauth.kiro.step1': 'Click the button below to open the authorization link in your browser',
|
||||
'oauth.kiro.step2': 'Log in with your {method} account',
|
||||
'oauth.kiro.step3': 'The page will close automatically after authorization',
|
||||
'oauth.kiro.step4': 'Refresh this page to view the credentials file',
|
||||
|
||||
// Config
|
||||
'config.title': 'Configuration Management',
|
||||
|
|
@ -721,11 +753,89 @@ export function setLanguage(lang) {
|
|||
currentLanguage = lang;
|
||||
localStorage.setItem('language', lang);
|
||||
updatePageLanguage();
|
||||
// 更新图片
|
||||
updateDashboardImages(lang);
|
||||
// 触发语言切换事件
|
||||
window.dispatchEvent(new CustomEvent('languageChanged', { detail: { language: lang } }));
|
||||
}
|
||||
}
|
||||
|
||||
// 更新仪表盘图片
|
||||
function updateDashboardImages(lang) {
|
||||
const sponsorImg = document.getElementById('sponsor-img');
|
||||
const sponsorTitle = document.getElementById('sponsor-title');
|
||||
const sponsorDesc = document.getElementById('sponsor-desc');
|
||||
|
||||
const wechatImg = document.getElementById('wechat-img');
|
||||
const wechatIcon = document.getElementById('wechat-icon');
|
||||
const wechatTitle = document.getElementById('wechat-title');
|
||||
const wechatDesc = document.getElementById('wechat-desc');
|
||||
|
||||
if (lang === 'en-US') {
|
||||
// 更新赞助图片
|
||||
if (sponsorImg) {
|
||||
sponsorImg.src = 'static/coffee.png';
|
||||
sponsorImg.alt = 'Buy me a coffee';
|
||||
if (sponsorTitle) {
|
||||
sponsorTitle.setAttribute('data-i18n', 'dashboard.contact.coffee');
|
||||
sponsorTitle.textContent = translations['en-US']['dashboard.contact.coffee'];
|
||||
}
|
||||
if (sponsorDesc) {
|
||||
sponsorDesc.setAttribute('data-i18n', 'dashboard.contact.coffeeDesc');
|
||||
sponsorDesc.textContent = translations['en-US']['dashboard.contact.coffeeDesc'];
|
||||
}
|
||||
}
|
||||
|
||||
// 更新联系方式图片 (WeChat -> X.com)
|
||||
if (wechatImg) {
|
||||
wechatImg.src = 'static/x.com.png';
|
||||
wechatImg.alt = 'X.com';
|
||||
if (wechatIcon) {
|
||||
wechatIcon.className = 'fab fa-x-twitter';
|
||||
}
|
||||
if (wechatTitle) {
|
||||
wechatTitle.setAttribute('data-i18n', 'dashboard.contact.x');
|
||||
wechatTitle.textContent = translations['en-US']['dashboard.contact.x'] || 'Follow on X.com';
|
||||
}
|
||||
if (wechatDesc) {
|
||||
wechatDesc.setAttribute('data-i18n', 'dashboard.contact.xDesc');
|
||||
wechatDesc.textContent = translations['en-US']['dashboard.contact.xDesc'] || 'Follow us on X for latest updates';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 更新赞助图片
|
||||
if (sponsorImg) {
|
||||
sponsorImg.src = 'static/sponsor.png';
|
||||
sponsorImg.alt = '赞助二维码';
|
||||
if (sponsorTitle) {
|
||||
sponsorTitle.setAttribute('data-i18n', 'dashboard.contact.sponsor');
|
||||
sponsorTitle.textContent = translations['zh-CN']['dashboard.contact.sponsor'];
|
||||
}
|
||||
if (sponsorDesc) {
|
||||
sponsorDesc.setAttribute('data-i18n', 'dashboard.contact.sponsorDesc');
|
||||
sponsorDesc.textContent = translations['zh-CN']['dashboard.contact.sponsorDesc'];
|
||||
}
|
||||
}
|
||||
|
||||
// 更新联系方式图片 (X.com -> WeChat)
|
||||
if (wechatImg) {
|
||||
wechatImg.src = 'static/wechat.png';
|
||||
wechatImg.alt = '微信二维码';
|
||||
if (wechatIcon) {
|
||||
wechatIcon.className = 'fab fa-weixin';
|
||||
}
|
||||
if (wechatTitle) {
|
||||
wechatTitle.setAttribute('data-i18n', 'dashboard.contact.wechat');
|
||||
wechatTitle.textContent = translations['zh-CN']['dashboard.contact.wechat'];
|
||||
}
|
||||
if (wechatDesc) {
|
||||
wechatDesc.setAttribute('data-i18n', 'dashboard.contact.wechatDesc');
|
||||
wechatDesc.textContent = translations['zh-CN']['dashboard.contact.wechatDesc'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取当前语言
|
||||
export function getCurrentLanguage() {
|
||||
return currentLanguage;
|
||||
|
|
@ -783,6 +893,8 @@ function updatePageLanguage() {
|
|||
export function initI18n() {
|
||||
// 设置初始语言
|
||||
updatePageLanguage();
|
||||
// 设置初始图片
|
||||
updateDashboardImages(currentLanguage);
|
||||
|
||||
// 监听 DOM 变化,自动翻译新添加的元素
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
|
|
|
|||
45
static/app/image-zoom.js
Normal file
45
static/app/image-zoom.js
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
* 图像点击放大功能模块
|
||||
*/
|
||||
|
||||
export function initImageZoom() {
|
||||
// 创建放大图层
|
||||
const overlay = document.createElement('div');
|
||||
overlay.className = 'image-zoom-overlay';
|
||||
overlay.innerHTML = '<img src="" alt="Zoomed Image">';
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
const zoomedImg = overlay.querySelector('img');
|
||||
|
||||
// 监听点击事件
|
||||
document.addEventListener('click', (e) => {
|
||||
const target = e.target;
|
||||
|
||||
// 如果点击的是可放大的二维码
|
||||
if (target.classList.contains('clickable-qr')) {
|
||||
zoomedImg.src = target.src;
|
||||
overlay.style.display = 'flex';
|
||||
setTimeout(() => {
|
||||
overlay.classList.add('show');
|
||||
}, 10);
|
||||
}
|
||||
|
||||
// 如果点击的是放大图层(或者其中的图片),则关闭
|
||||
if (overlay.classList.contains('show') && (target === overlay || target === zoomedImg)) {
|
||||
overlay.classList.remove('show');
|
||||
setTimeout(() => {
|
||||
overlay.style.display = 'none';
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
|
||||
// ESC 键关闭
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape' && overlay.classList.contains('show')) {
|
||||
overlay.classList.remove('show');
|
||||
setTimeout(() => {
|
||||
overlay.style.display = 'none';
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -362,36 +362,36 @@ function showKiroAuthMethodSelector(providerType) {
|
|||
modal.innerHTML = `
|
||||
<div class="modal-content" style="max-width: 500px;">
|
||||
<div class="modal-header">
|
||||
<h3><i class="fas fa-key"></i> 选择认证方式</h3>
|
||||
<h3><i class="fas fa-key"></i> <span data-i18n="oauth.kiro.selectMethod">${t('oauth.kiro.selectMethod')}</span></h3>
|
||||
<button class="modal-close">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="auth-method-options" style="display: flex; flex-direction: column; gap: 12px;">
|
||||
<button class="auth-method-btn" data-method="google" style="display: flex; align-items: center; gap: 12px; padding: 16px; border: 2px solid #e0e0e0; border-radius: 8px; background: white; cursor: pointer; transition: all 0.2s;">
|
||||
<!-- <button class="auth-method-btn" data-method="google" style="display: flex; align-items: center; gap: 12px; padding: 16px; border: 2px solid #e0e0e0; border-radius: 8px; background: white; cursor: pointer; transition: all 0.2s;">
|
||||
<i class="fab fa-google" style="font-size: 24px; color: #4285f4;"></i>
|
||||
<div style="text-align: left;">
|
||||
<div style="font-weight: 600; color: #333;">Google 账号登录</div>
|
||||
<div style="font-size: 12px; color: #666;">使用 Google 账号进行社交登录</div>
|
||||
<div style="font-weight: 600; color: #333;" data-i18n="oauth.kiro.google">${t('oauth.kiro.google')}</div>
|
||||
<div style="font-size: 12px; color: #666;" data-i18n="oauth.kiro.googleDesc">${t('oauth.kiro.googleDesc')}</div>
|
||||
</div>
|
||||
</button>
|
||||
<button class="auth-method-btn" data-method="github" style="display: flex; align-items: center; gap: 12px; padding: 16px; border: 2px solid #e0e0e0; border-radius: 8px; background: white; cursor: pointer; transition: all 0.2s;">
|
||||
<i class="fab fa-github" style="font-size: 24px; color: #333;"></i>
|
||||
<div style="text-align: left;">
|
||||
<div style="font-weight: 600; color: #333;">GitHub 账号登录</div>
|
||||
<div style="font-size: 12px; color: #666;">使用 GitHub 账号进行社交登录</div>
|
||||
<div style="font-weight: 600; color: #333;" data-i18n="oauth.kiro.github">${t('oauth.kiro.github')}</div>
|
||||
<div style="font-size: 12px; color: #666;" data-i18n="oauth.kiro.githubDesc">${t('oauth.kiro.githubDesc')}</div>
|
||||
</div>
|
||||
</button>
|
||||
</button> -->
|
||||
<button class="auth-method-btn" data-method="builder-id" style="display: flex; align-items: center; gap: 12px; padding: 16px; border: 2px solid #e0e0e0; border-radius: 8px; background: white; cursor: pointer; transition: all 0.2s;">
|
||||
<i class="fab fa-aws" style="font-size: 24px; color: #ff9900;"></i>
|
||||
<div style="text-align: left;">
|
||||
<div style="font-weight: 600; color: #333;">AWS Builder ID</div>
|
||||
<div style="font-size: 12px; color: #666;">使用 AWS Builder ID 进行设备码授权</div>
|
||||
<div style="font-weight: 600; color: #333;" data-i18n="oauth.kiro.awsBuilder">${t('oauth.kiro.awsBuilder')}</div>
|
||||
<div style="font-size: 12px; color: #666;" data-i18n="oauth.kiro.awsBuilderDesc">${t('oauth.kiro.awsBuilderDesc')}</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="modal-cancel">${t('modal.provider.cancel')}</button>
|
||||
<button class="modal-cancel" data-i18n="modal.provider.cancel">${t('modal.provider.cancel')}</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
|
@ -502,15 +502,16 @@ function showAuthModal(authUrl, authInfo) {
|
|||
`;
|
||||
} else if (authInfo.provider === 'claude-kiro-oauth') {
|
||||
const methodDisplay = authInfo.authMethod === 'builder-id' ? 'AWS Builder ID' : `Social (${authInfo.socialProvider || 'Google'})`;
|
||||
const methodAccount = authInfo.authMethod === 'builder-id' ? 'AWS Builder ID' : authInfo.socialProvider || 'Google';
|
||||
instructionsHtml = `
|
||||
<div class="auth-instructions">
|
||||
<h4 data-i18n="oauth.modal.steps">${t('oauth.modal.steps')}</h4>
|
||||
<p><strong>认证方式:</strong> ${methodDisplay}</p>
|
||||
<p><strong data-i18n="oauth.kiro.authMethodLabel">${t('oauth.kiro.authMethodLabel')}</strong> ${methodDisplay}</p>
|
||||
<ol>
|
||||
<li>点击下方按钮在浏览器中打开授权链接</li>
|
||||
<li>使用您的 ${authInfo.authMethod === 'builder-id' ? 'AWS Builder ID' : authInfo.socialProvider || 'Google'} 账号登录</li>
|
||||
<li>授权完成后页面会自动关闭</li>
|
||||
<li>刷新本页面查看凭据文件</li>
|
||||
<li data-i18n="oauth.kiro.step1">${t('oauth.kiro.step1')}</li>
|
||||
<li data-i18n="oauth.kiro.step2" data-i18n-params='{"method":"${methodAccount}"}'>${t('oauth.kiro.step2', { method: methodAccount })}</li>
|
||||
<li data-i18n="oauth.kiro.step3">${t('oauth.kiro.step3')}</li>
|
||||
<li data-i18n="oauth.kiro.step4">${t('oauth.kiro.step4')}</li>
|
||||
</ol>
|
||||
</div>
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -2990,6 +2990,66 @@ input:checked + .toggle-slider:before {
|
|||
}
|
||||
}
|
||||
|
||||
/* Dashboard Top Row Layout */
|
||||
.dashboard-top-row {
|
||||
display: flex;
|
||||
gap: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
align-items: stretch;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.dashboard-top-row .stats-grid {
|
||||
flex: 1;
|
||||
margin-bottom: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.dashboard-top-row .stats-grid .stat-card {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.dashboard-top-row .dashboard-contact {
|
||||
flex: 1;
|
||||
margin-top: 0;
|
||||
padding-top: 0;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.dashboard-top-row .dashboard-contact .contact-grid {
|
||||
margin-top: 0;
|
||||
height: 100%;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.dashboard-top-row .dashboard-contact .contact-card {
|
||||
padding: 1.25rem;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.dashboard-top-row .dashboard-contact .qr-container {
|
||||
margin: 0.75rem 0;
|
||||
}
|
||||
|
||||
.dashboard-top-row .dashboard-contact .qr-code {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.dashboard-top-row .dashboard-contact .contact-card h3 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.dashboard-top-row .dashboard-contact .qr-description {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
/* Contact and Sponsor Section */
|
||||
.contact-grid {
|
||||
display: grid;
|
||||
|
|
@ -3051,6 +3111,45 @@ input:checked + .toggle-slider:before {
|
|||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.clickable-qr {
|
||||
cursor: zoom-in;
|
||||
}
|
||||
|
||||
/* Image Zoom Overlay */
|
||||
.image-zoom-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.85);
|
||||
display: none;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 2000;
|
||||
cursor: zoom-out;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.image-zoom-overlay.show {
|
||||
display: flex;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.image-zoom-overlay img {
|
||||
max-width: 90%;
|
||||
max-height: 90%;
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
|
||||
transform: scale(0.9);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.image-zoom-overlay.show img {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.qr-description {
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-secondary);
|
||||
|
|
@ -3059,6 +3158,21 @@ input:checked + .toggle-slider:before {
|
|||
}
|
||||
|
||||
/* 响应式调整 */
|
||||
@media (max-width: 1024px) {
|
||||
.dashboard-top-row {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.dashboard-top-row .stats-grid {
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.dashboard-top-row .dashboard-contact .qr-code {
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.contact-grid {
|
||||
grid-template-columns: 1fr;
|
||||
|
|
|
|||
BIN
static/coffee.png
Normal file
BIN
static/coffee.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.7 KiB |
|
|
@ -62,17 +62,40 @@
|
|||
<!-- Dashboard Section -->
|
||||
<section id="dashboard" class="section active" aria-labelledby="dashboard-title">
|
||||
<h2 id="dashboard-title" data-i18n="dashboard.title">系统概览</h2>
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon">
|
||||
<i class="fas fa-clock"></i>
|
||||
<div class="dashboard-top-row">
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon">
|
||||
<i class="fas fa-clock"></i>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<h3 id="uptime">--</h3>
|
||||
<p data-i18n="dashboard.uptime">运行时间</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<h3 id="uptime">--</h3>
|
||||
<p data-i18n="dashboard.uptime">运行时间</p>
|
||||
</div>
|
||||
|
||||
<!-- Contact and Sponsor Section -->
|
||||
<div class="contact-section dashboard-contact">
|
||||
<div class="contact-grid">
|
||||
<div class="contact-card">
|
||||
<h3><i id="wechat-icon" class="fab fa-weixin"></i> <span id="wechat-title" data-i18n="dashboard.contact.wechat">扫码进群,注明来意</span></h3>
|
||||
<div class="qr-container">
|
||||
<img src="static/wechat.png" id="wechat-img" alt="微信二维码" class="qr-code clickable-qr">
|
||||
</div>
|
||||
<p class="qr-description" id="wechat-desc" data-i18n="dashboard.contact.wechatDesc">添加微信获取更多技术支持和交流</p>
|
||||
</div>
|
||||
<div class="contact-card" id="sponsor-card">
|
||||
<h3><i class="fas fa-heart"></i> <span id="sponsor-title" data-i18n="dashboard.contact.sponsor">扫码赞助</span></h3>
|
||||
<div class="qr-container">
|
||||
<img src="static/sponsor.png" id="sponsor-img" alt="赞助二维码" class="qr-code clickable-qr">
|
||||
</div>
|
||||
<p class="qr-description" id="sponsor-desc" data-i18n="dashboard.contact.sponsorDesc">您的赞助是项目持续发展的动力</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- System Information Panel -->
|
||||
<div class="system-info-panel">
|
||||
<h3 data-i18n="dashboard.systemInfo">系统信息</h3>
|
||||
|
|
@ -434,26 +457,6 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Contact and Sponsor Section -->
|
||||
<div class="contact-section">
|
||||
<h3><i class="fas fa-qrcode"></i> <span data-i18n="dashboard.contact.title">联系与赞助</span></h3>
|
||||
<div class="contact-grid">
|
||||
<div class="contact-card">
|
||||
<h3><i class="fab fa-weixin"></i> <span data-i18n="dashboard.contact.wechat">扫码进群,注明来意</span></h3>
|
||||
<div class="qr-container">
|
||||
<img src="static/wechat.png" alt="微信二维码" class="qr-code">
|
||||
</div>
|
||||
<p class="qr-description" data-i18n="dashboard.contact.wechatDesc">添加微信获取更多技术支持和交流</p>
|
||||
</div>
|
||||
<div class="contact-card">
|
||||
<h3><i class="fas fa-heart"></i> <span data-i18n="dashboard.contact.sponsor">扫码赞助</span></h3>
|
||||
<div class="qr-container">
|
||||
<img src="static/sponsor.png" alt="赞助二维码" class="qr-code">
|
||||
</div>
|
||||
<p class="qr-description" data-i18n="dashboard.contact.sponsorDesc">您的赞助是项目持续发展的动力</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Configuration Section -->
|
||||
|
|
@ -633,6 +636,9 @@
|
|||
<button type="button" class="btn btn-outline upload-btn" data-target="kiroOauthCredsFilePath" data-i18n-title="common.upload" title="上传文件" aria-label="上传文件" data-i18n-aria-label="common.upload">
|
||||
<i class="fas fa-upload"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline generate-creds-btn" data-target="kiroOauthCredsFilePath" data-provider="claude-kiro-oauth" data-i18n-title="common.generate" title="生成凭据文件">
|
||||
<i class="fas fa-magic"></i>
|
||||
</button>
|
||||
</div>
|
||||
<small class="form-text">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
|
|
|
|||
BIN
static/x.com.png
Normal file
BIN
static/x.com.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.9 KiB |
Loading…
Reference in a new issue