fix: comprehensive dark theme removal - all admin pages, storage, models, achievements, developer tools, front CSS
This commit is contained in:
1 parent
a6522d5069
commit
c729f61831
5 files changed
+82
-82
No files matched your search
@@ -269,65 +269,65 @@ const Achievement: React.FC = () => {
|
|||||||
return (
|
return (
|
||||||
<div className="admin-page" style={{ padding: '24px' }}>
|
<div className="admin-page" style={{ padding: '24px' }}>
|
||||||
<style>{`
|
<style>{`
|
||||||
.ach-tabs { display: flex; gap: 4px; margin-bottom: 24px; border-bottom: 1px solid rgba(255,255,255,0.08); }
|
.ach-tabs { display: flex; gap: 4px; margin-bottom: 24px; border-bottom: 1px solid rgba(0,0,0,0.08); }
|
||||||
.ach-tab { padding: 10px 20px; cursor: pointer; color: rgba(255,255,255,0.5); font-size: 14px; border-bottom: 2px solid transparent; transition: all 0.2s; background: none; border-top: none; border-left: none; border-right: none; }
|
.ach-tab { padding: 10px 20px; cursor: pointer; color: rgba(255,255,255,0.5); font-size: 14px; border-bottom: 2px solid transparent; transition: all 0.2s; background: none; border-top: none; border-left: none; border-right: none; }
|
||||||
.ach-tab:hover { color: rgba(255,255,255,0.8); }
|
.ach-tab:hover { color: rgba(255,255,255,0.8); }
|
||||||
.ach-tab.active { color: #6C63FF; border-bottom-color: #6C63FF; }
|
.ach-tab.active { color: #6C63FF; border-bottom-color: #6C63FF; }
|
||||||
.ach-stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 16px; margin-bottom: 24px; }
|
.ach-stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 16px; margin-bottom: 24px; }
|
||||||
.ach-stat-card { background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.08); border-radius: 12px; padding: 20px; }
|
.ach-stat-card { background: rgba(0,0,0,0.02); border: 1px solid rgba(0,0,0,0.08); border-radius: 12px; padding: 20px; }
|
||||||
.ach-stat-card .label { font-size: 12px; color: rgba(255,255,255,0.4); margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.5px; }
|
.ach-stat-card .label { font-size: 12px; color: rgba(0,0,0,0.4); margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.5px; }
|
||||||
.ach-stat-card .value { font-size: 28px; font-weight: 700; color: #fff; }
|
.ach-stat-card .value { font-size: 28px; font-weight: 700; color: #1A1F36; }
|
||||||
.ach-cat-bar { display: flex; align-items: center; gap: 12px; margin-bottom: 12px; }
|
.ach-cat-bar { display: flex; align-items: center; gap: 12px; margin-bottom: 12px; }
|
||||||
.ach-cat-bar .cat-label { width: 80px; font-size: 13px; color: rgba(255,255,255,0.6); }
|
.ach-cat-bar .cat-label { width: 80px; font-size: 13px; color: rgba(0,0,0,0.6); }
|
||||||
.ach-cat-bar .cat-track { flex: 1; height: 8px; background: rgba(255,255,255,0.06); border-radius: 4px; overflow: hidden; }
|
.ach-cat-bar .cat-track { flex: 1; height: 8px; background: rgba(0,0,0,0.06); border-radius: 4px; overflow: hidden; }
|
||||||
.ach-cat-bar .cat-fill { height: 100%; border-radius: 4px; transition: width 0.3s; }
|
.ach-cat-bar .cat-fill { height: 100%; border-radius: 4px; transition: width 0.3s; }
|
||||||
.ach-cat-bar .cat-count { width: 30px; text-align: right; font-size: 13px; color: rgba(255,255,255,0.8); font-weight: 600; }
|
.ach-cat-bar .cat-count { width: 30px; text-align: right; font-size: 13px; color: rgba(255,255,255,0.8); font-weight: 600; }
|
||||||
.ach-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 16px; }
|
.ach-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 16px; }
|
||||||
.ach-card { background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.08); border-radius: 12px; padding: 20px; transition: all 0.2s; position: relative; }
|
.ach-card { background: rgba(0,0,0,0.02); border: 1px solid rgba(0,0,0,0.08); border-radius: 12px; padding: 20px; transition: all 0.2s; position: relative; }
|
||||||
.ach-card:hover { border-color: rgba(108,99,255,0.3); background: rgba(255,255,255,0.05); }
|
.ach-card:hover { border-color: rgba(108,99,255,0.3); background: rgba(0,0,0,0.04); }
|
||||||
.ach-card.completed { border-color: rgba(0,212,170,0.3); }
|
.ach-card.completed { border-color: rgba(0,212,170,0.3); }
|
||||||
.ach-card .ach-icon { width: 48px; height: 48px; border-radius: 12px; display: flex; align-items: center; justify-content: center; font-size: 24px; margin-bottom: 12px; }
|
.ach-card .ach-icon { width: 48px; height: 48px; border-radius: 12px; display: flex; align-items: center; justify-content: center; font-size: 24px; margin-bottom: 12px; }
|
||||||
.ach-card .ach-name { font-size: 16px; font-weight: 600; color: #fff; margin-bottom: 4px; }
|
.ach-card .ach-name { font-size: 16px; font-weight: 600; color: #1A1F36; margin-bottom: 4px; }
|
||||||
.ach-card .ach-desc { font-size: 13px; color: rgba(255,255,255,0.4); margin-bottom: 12px; line-height: 1.5; }
|
.ach-card .ach-desc { font-size: 13px; color: rgba(0,0,0,0.4); margin-bottom: 12px; line-height: 1.5; }
|
||||||
.ach-card .ach-meta { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 12px; }
|
.ach-card .ach-meta { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 12px; }
|
||||||
.ach-badge { padding: 3px 8px; border-radius: 4px; font-size: 11px; font-weight: 500; }
|
.ach-badge { padding: 3px 8px; border-radius: 4px; font-size: 11px; font-weight: 500; }
|
||||||
.ach-progress-bar { height: 6px; background: rgba(255,255,255,0.06); border-radius: 3px; overflow: hidden; margin-bottom: 8px; }
|
.ach-progress-bar { height: 6px; background: rgba(0,0,0,0.06); border-radius: 3px; overflow: hidden; margin-bottom: 8px; }
|
||||||
.ach-progress-fill { height: 100%; border-radius: 3px; background: linear-gradient(90deg, #6C63FF, #8B80FF); transition: width 0.3s; }
|
.ach-progress-fill { height: 100%; border-radius: 3px; background: linear-gradient(90deg, #6C63FF, #8B80FF); transition: width 0.3s; }
|
||||||
.ach-progress-fill.done { background: linear-gradient(90deg, #00D4AA, #00E4B4); }
|
.ach-progress-fill.done { background: linear-gradient(90deg, #00D4AA, #00E4B4); }
|
||||||
.ach-progress-text { display: flex; justify-content: space-between; font-size: 12px; color: rgba(255,255,255,0.4); }
|
.ach-progress-text { display: flex; justify-content: space-between; font-size: 12px; color: rgba(0,0,0,0.4); }
|
||||||
.ach-actions { display: flex; gap: 8px; margin-top: 12px; }
|
.ach-actions { display: flex; gap: 8px; margin-top: 12px; }
|
||||||
.ach-btn { padding: 6px 12px; border-radius: 6px; font-size: 12px; cursor: pointer; border: none; transition: all 0.2s; }
|
.ach-btn { padding: 6px 12px; border-radius: 6px; font-size: 12px; cursor: pointer; border: none; transition: all 0.2s; }
|
||||||
.ach-btn-primary { background: #6C63FF; color: #fff; }
|
.ach-btn-primary { background: #6C63FF; color: #1A1F36; }
|
||||||
.ach-btn-primary:hover { background: #5B52E0; }
|
.ach-btn-primary:hover { background: #5B52E0; }
|
||||||
.ach-btn-danger { background: rgba(255,107,107,0.15); color: #FF6B6B; }
|
.ach-btn-danger { background: rgba(255,107,107,0.15); color: #FF6B6B; }
|
||||||
.ach-btn-danger:hover { background: rgba(255,107,107,0.25); }
|
.ach-btn-danger:hover { background: rgba(255,107,107,0.25); }
|
||||||
.ach-btn-success { background: rgba(0,212,170,0.15); color: #00D4AA; }
|
.ach-btn-success { background: rgba(0,212,170,0.15); color: #00D4AA; }
|
||||||
.ach-btn-success:hover { background: rgba(0,212,170,0.25); }
|
.ach-btn-success:hover { background: rgba(0,212,170,0.25); }
|
||||||
.ach-btn-ghost { background: rgba(255,255,255,0.06); color: rgba(255,255,255,0.6); }
|
.ach-btn-ghost { background: rgba(0,0,0,0.06); color: rgba(0,0,0,0.6); }
|
||||||
.ach-btn-ghost:hover { background: rgba(255,255,255,0.1); }
|
.ach-btn-ghost:hover { background: rgba(0,0,0,0.10); }
|
||||||
.ach-btn:disabled { opacity: 0.4; cursor: not-allowed; }
|
.ach-btn:disabled { opacity: 0.4; cursor: not-allowed; }
|
||||||
.ach-toolbar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
|
.ach-toolbar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
|
||||||
.ach-select { background: rgba(255,255,255,0.06); border: 1px solid rgba(255,255,255,0.1); border-radius: 8px; padding: 8px 12px; color: #fff; font-size: 13px; }
|
.ach-select { background: rgba(0,0,0,0.06); border: 1px solid rgba(0,0,0,0.10); border-radius: 8px; padding: 8px 12px; color: #1A1F36; font-size: 13px; }
|
||||||
.ach-select option { background: #1A1A2E; }
|
.ach-select option { background: #1A1A2E; }
|
||||||
.ach-modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.6); display: flex; align-items: center; justify-content: center; z-index: 1000; }
|
.ach-modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.6); display: flex; align-items: center; justify-content: center; z-index: 1000; }
|
||||||
.ach-modal { background: #1E1E36; border: 1px solid rgba(255,255,255,0.1); border-radius: 16px; padding: 28px; width: 480px; max-width: 90vw; max-height: 90vh; overflow-y: auto; }
|
.ach-modal { background: #1E1E36; border: 1px solid rgba(0,0,0,0.10); border-radius: 16px; padding: 28px; width: 480px; max-width: 90vw; max-height: 90vh; overflow-y: auto; }
|
||||||
.ach-modal h2 { font-size: 20px; color: #fff; margin-bottom: 20px; }
|
.ach-modal h2 { font-size: 20px; color: #1A1F36; margin-bottom: 20px; }
|
||||||
.ach-field { margin-bottom: 16px; }
|
.ach-field { margin-bottom: 16px; }
|
||||||
.ach-field label { display: block; font-size: 13px; color: rgba(255,255,255,0.5); margin-bottom: 6px; }
|
.ach-field label { display: block; font-size: 13px; color: rgba(255,255,255,0.5); margin-bottom: 6px; }
|
||||||
.ach-field input, .ach-field select, .ach-field textarea { width: 100%; background: rgba(255,255,255,0.06); border: 1px solid rgba(255,255,255,0.1); border-radius: 8px; padding: 10px 12px; color: #fff; font-size: 14px; box-sizing: border-box; }
|
.ach-field input, .ach-field select, .ach-field textarea { width: 100%; background: rgba(0,0,0,0.06); border: 1px solid rgba(0,0,0,0.10); border-radius: 8px; padding: 10px 12px; color: #1A1F36; font-size: 14px; box-sizing: border-box; }
|
||||||
.ach-field input:focus, .ach-field select:focus, .ach-field textarea:focus { outline: none; border-color: #6C63FF; }
|
.ach-field input:focus, .ach-field select:focus, .ach-field textarea:focus { outline: none; border-color: #6C63FF; }
|
||||||
.ach-field select option { background: #1A1A2E; }
|
.ach-field select option { background: #1A1A2E; }
|
||||||
.ach-field-row { display: flex; gap: 12px; }
|
.ach-field-row { display: flex; gap: 12px; }
|
||||||
.ach-field-row .ach-field { flex: 1; }
|
.ach-field-row .ach-field { flex: 1; }
|
||||||
.ach-empty { text-align: center; padding: 60px 20px; color: rgba(255,255,255,0.3); }
|
.ach-empty { text-align: center; padding: 60px 20px; color: rgba(255,255,255,0.3); }
|
||||||
.ach-lb-table { width: 100%; border-collapse: collapse; }
|
.ach-lb-table { width: 100%; border-collapse: collapse; }
|
||||||
.ach-lb-table th { text-align: left; padding: 12px 16px; font-size: 12px; color: rgba(255,255,255,0.4); text-transform: uppercase; letter-spacing: 0.5px; border-bottom: 1px solid rgba(255,255,255,0.08); }
|
.ach-lb-table th { text-align: left; padding: 12px 16px; font-size: 12px; color: rgba(0,0,0,0.4); text-transform: uppercase; letter-spacing: 0.5px; border-bottom: 1px solid rgba(0,0,0,0.08); }
|
||||||
.ach-lb-table td { padding: 14px 16px; font-size: 14px; color: rgba(255,255,255,0.8); border-bottom: 1px solid rgba(255,255,255,0.04); }
|
.ach-lb-table td { padding: 14px 16px; font-size: 14px; color: rgba(255,255,255,0.8); border-bottom: 1px solid rgba(255,255,255,0.04); }
|
||||||
.ach-lb-rank { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; border-radius: 50%; font-size: 13px; font-weight: 700; }
|
.ach-lb-rank { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; border-radius: 50%; font-size: 13px; font-weight: 700; }
|
||||||
.ach-lb-rank.gold { background: rgba(255,184,0,0.15); color: #FFB800; }
|
.ach-lb-rank.gold { background: rgba(255,184,0,0.15); color: #FFB800; }
|
||||||
.ach-lb-rank.silver { background: rgba(192,192,192,0.15); color: #C0C0C0; }
|
.ach-lb-rank.silver { background: rgba(192,192,192,0.15); color: #C0C0C0; }
|
||||||
.ach-lb-rank.bronze { background: rgba(205,127,50,0.15); color: #CD7F32; }
|
.ach-lb-rank.bronze { background: rgba(205,127,50,0.15); color: #CD7F32; }
|
||||||
.ach-lb-rank.normal { background: rgba(255,255,255,0.06); color: rgba(255,255,255,0.5); }
|
.ach-lb-rank.normal { background: rgba(0,0,0,0.06); color: rgba(255,255,255,0.5); }
|
||||||
.ach-error { background: rgba(255,107,107,0.1); border: 1px solid rgba(255,107,107,0.2); border-radius: 8px; padding: 12px 16px; color: #FF6B6B; font-size: 13px; margin-bottom: 16px; }
|
.ach-error { background: rgba(255,107,107,0.1); border: 1px solid rgba(255,107,107,0.2); border-radius: 8px; padding: 12px 16px; color: #FF6B6B; font-size: 13px; margin-bottom: 16px; }
|
||||||
`}</style>
|
`}</style>
|
||||||
|
|
||||||
@@ -381,7 +381,7 @@ const Achievement: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ background: 'rgba(255,255,255,0.03)', border: '1px solid rgba(255,255,255,0.08)', borderRadius: '12px', padding: '24px' }}>
|
<div style={{ background: 'rgba(0,0,0,0.02)', border: '1px solid rgba(0,0,0,0.08)', borderRadius: '12px', padding: '24px' }}>
|
||||||
<h3 style={{ fontSize: '16px', color: '#fff', marginBottom: '20px' }}>分类分布</h3>
|
<h3 style={{ fontSize: '16px', color: '#fff', marginBottom: '20px' }}>分类分布</h3>
|
||||||
{Object.entries(stats.by_category).map(([cat, count]) => {
|
{Object.entries(stats.by_category).map(([cat, count]) => {
|
||||||
const pct = stats.total_achievements > 0 ? (count / stats.total_achievements) * 100 : 0;
|
const pct = stats.total_achievements > 0 ? (count / stats.total_achievements) * 100 : 0;
|
||||||
@@ -432,7 +432,7 @@ const Achievement: React.FC = () => {
|
|||||||
<div className="ach-desc">{ach.description || '暂无描述'}</div>
|
<div className="ach-desc">{ach.description || '暂无描述'}</div>
|
||||||
<div className="ach-meta">
|
<div className="ach-meta">
|
||||||
<span className="ach-badge" style={{ background: `${color}20`, color }}>{CATEGORY_LABELS[ach.category] || ach.category}</span>
|
<span className="ach-badge" style={{ background: `${color}20`, color }}>{CATEGORY_LABELS[ach.category] || ach.category}</span>
|
||||||
<span className="ach-badge" style={{ background: 'rgba(255,255,255,0.06)', color: 'rgba(255,255,255,0.6)' }}>
|
<span className="ach-badge" style={{ background: 'rgba(0,0,0,0.06)', color: 'rgba(0,0,0,0.6)' }}>
|
||||||
{CRITERIA_LABELS[ach.criteria_type] || ach.criteria_type}: {ach.criteria_value}
|
{CRITERIA_LABELS[ach.criteria_type] || ach.criteria_type}: {ach.criteria_value}
|
||||||
</span>
|
</span>
|
||||||
<span className="ach-badge" style={{ background: 'rgba(255,184,0,0.1)', color: '#FFB800' }}>
|
<span className="ach-badge" style={{ background: 'rgba(255,184,0,0.1)', color: '#FFB800' }}>
|
||||||
@@ -558,7 +558,7 @@ const Achievement: React.FC = () => {
|
|||||||
<td>{entry.username || entry.user_id || '匿名用户'}</td>
|
<td>{entry.username || entry.user_id || '匿名用户'}</td>
|
||||||
<td>{entry.achievement_count || 0}</td>
|
<td>{entry.achievement_count || 0}</td>
|
||||||
<td style={{ color: '#FFB800', fontWeight: 600 }}>{entry.total_points || 0}</td>
|
<td style={{ color: '#FFB800', fontWeight: 600 }}>{entry.total_points || 0}</td>
|
||||||
<td style={{ color: 'rgba(255,255,255,0.4)' }}>
|
<td style={{ color: 'rgba(0,0,0,0.4)' }}>
|
||||||
{entry.last_completed ? new Date(entry.last_completed).toLocaleDateString('zh-CN') : '-'}
|
{entry.last_completed ? new Date(entry.last_completed).toLocaleDateString('zh-CN') : '-'}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -221,7 +221,7 @@ export default function AdminDeveloperTools() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{testResult && (
|
{testResult && (
|
||||||
<div className="dev-test-result" style={{ margin: '12px 0', padding: '12px', background: 'rgba(255,255,255,0.05)', borderRadius: '8px', border: `1px solid ${testResult.status === 'success' ? '#52c41a' : '#ff4d4f'}` }}>
|
<div className="dev-test-result" style={{ margin: '12px 0', padding: '12px', background: 'rgba(0,0,0,0.04)', borderRadius: '8px', border: `1px solid ${testResult.status === 'success' ? '#52c41a' : '#ff4d4f'}` }}>
|
||||||
<strong>Test Result: </strong>
|
<strong>Test Result: </strong>
|
||||||
<span style={{ color: testResult.status === 'success' ? '#52c41a' : '#ff4d4f' }}>{testResult.status}</span>
|
<span style={{ color: testResult.status === 'success' ? '#52c41a' : '#ff4d4f' }}>{testResult.status}</span>
|
||||||
{testResult.response_code && <span> | HTTP {testResult.response_code}</span>}
|
{testResult.response_code && <span> | HTTP {testResult.response_code}</span>}
|
||||||
@@ -307,11 +307,11 @@ export default function AdminDeveloperTools() {
|
|||||||
|
|
||||||
{/* Supported Events Info */}
|
{/* Supported Events Info */}
|
||||||
{webhookEvents.length > 0 && (
|
{webhookEvents.length > 0 && (
|
||||||
<div style={{ marginTop: '20px', padding: '16px', background: 'rgba(255,255,255,0.03)', borderRadius: '8px' }}>
|
<div style={{ marginTop: '20px', padding: '16px', background: 'rgba(0,0,0,0.02)', borderRadius: '8px' }}>
|
||||||
<h4 style={{ margin: '0 0 12px', color: 'rgba(255,255,255,0.85)' }}>Supported Events</h4>
|
<h4 style={{ margin: '0 0 12px', color: 'rgba(255,255,255,0.85)' }}>Supported Events</h4>
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))', gap: '8px' }}>
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))', gap: '8px' }}>
|
||||||
{webhookEvents.map(ev => (
|
{webhookEvents.map(ev => (
|
||||||
<div key={ev.value} style={{ padding: '8px 12px', background: 'rgba(255,255,255,0.05)', borderRadius: '6px' }}>
|
<div key={ev.value} style={{ padding: '8px 12px', background: 'rgba(0,0,0,0.04)', borderRadius: '6px' }}>
|
||||||
<code style={{ color: '#69b1ff' }}>{ev.value}</code>
|
<code style={{ color: '#69b1ff' }}>{ev.value}</code>
|
||||||
<div style={{ fontSize: '12px', color: 'rgba(255,255,255,0.45)', marginTop: '2px' }}>{ev.desc}</div>
|
<div style={{ fontSize: '12px', color: 'rgba(255,255,255,0.45)', marginTop: '2px' }}>{ev.desc}</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -339,7 +339,7 @@ export default function AdminDeveloperTools() {
|
|||||||
<label>Events (leave empty for all)</label>
|
<label>Events (leave empty for all)</label>
|
||||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px', marginTop: '8px' }}>
|
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px', marginTop: '8px' }}>
|
||||||
{webhookEvents.map(ev => (
|
{webhookEvents.map(ev => (
|
||||||
<label key={ev.value} style={{ display: 'flex', alignItems: 'center', gap: '4px', cursor: 'pointer', padding: '4px 10px', background: webhookForm.events.includes(ev.value) ? 'rgba(22,119,255,0.2)' : 'rgba(255,255,255,0.05)', borderRadius: '6px', fontSize: '13px' }}>
|
<label key={ev.value} style={{ display: 'flex', alignItems: 'center', gap: '4px', cursor: 'pointer', padding: '4px 10px', background: webhookForm.events.includes(ev.value) ? 'rgba(22,119,255,0.2)' : 'rgba(0,0,0,0.04)', borderRadius: '6px', fontSize: '13px' }}>
|
||||||
<input type="checkbox" checked={webhookForm.events.includes(ev.value)} onChange={e => {
|
<input type="checkbox" checked={webhookForm.events.includes(ev.value)} onChange={e => {
|
||||||
if (e.target.checked) setWebhookForm({ ...webhookForm, events: [...webhookForm.events, ev.value] });
|
if (e.target.checked) setWebhookForm({ ...webhookForm, events: [...webhookForm.events, ev.value] });
|
||||||
else setWebhookForm({ ...webhookForm, events: webhookForm.events.filter(x => x !== ev.value) });
|
else setWebhookForm({ ...webhookForm, events: webhookForm.events.filter(x => x !== ev.value) });
|
||||||
@@ -372,7 +372,7 @@ export default function AdminDeveloperTools() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{sdkPackages.filter(p => p.language === selectedSdk).map(pkg => (
|
{sdkPackages.filter(p => p.language === selectedSdk).map(pkg => (
|
||||||
<div key={pkg.language} style={{ padding: '20px', background: 'rgba(255,255,255,0.03)', borderRadius: '12px' }}>
|
<div key={pkg.language} style={{ padding: '20px', background: 'rgba(0,0,0,0.02)', borderRadius: '12px' }}>
|
||||||
{/* Package Info */}
|
{/* Package Info */}
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: '16px' }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: '16px' }}>
|
||||||
<div>
|
<div>
|
||||||
@@ -442,7 +442,7 @@ export default function AdminDeveloperTools() {
|
|||||||
{/* Section Header */}
|
{/* Section Header */}
|
||||||
<div
|
<div
|
||||||
onClick={() => setExpandedSection(isExpanded ? null : section.section)}
|
onClick={() => setExpandedSection(isExpanded ? null : section.section)}
|
||||||
style={{ padding: '14px 16px', background: 'rgba(255,255,255,0.05)', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}
|
style={{ padding: '14px 16px', background: 'rgba(0,0,0,0.04)', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}
|
||||||
>
|
>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
|
||||||
<span style={{ fontSize: '18px' }}>
|
<span style={{ fontSize: '18px' }}>
|
||||||
@@ -462,7 +462,7 @@ export default function AdminDeveloperTools() {
|
|||||||
const epKey = `${section.section}-${ep.path}`;
|
const epKey = `${section.section}-${ep.path}`;
|
||||||
const isEpExpanded = expandedEndpoint === epKey;
|
const isEpExpanded = expandedEndpoint === epKey;
|
||||||
return (
|
return (
|
||||||
<div key={epKey} style={{ borderTop: '1px solid rgba(255,255,255,0.05)' }}>
|
<div key={epKey} style={{ borderTop: '1px solid rgba(0,0,0,0.04)' }}>
|
||||||
<div
|
<div
|
||||||
onClick={() => setExpandedEndpoint(isEpExpanded ? null : epKey)}
|
onClick={() => setExpandedEndpoint(isEpExpanded ? null : epKey)}
|
||||||
style={{ padding: '10px 16px', cursor: 'pointer', display: 'flex', alignItems: 'center', gap: '12px' }}
|
style={{ padding: '10px 16px', cursor: 'pointer', display: 'flex', alignItems: 'center', gap: '12px' }}
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ const ModelStatsPanel: React.FC<{ models: AIModel[]; providers: ModelProvider[]
|
|||||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(150px, 1fr))', gap: 12, marginBottom: 20 }}>
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(150px, 1fr))', gap: 12, marginBottom: 20 }}>
|
||||||
{items.map((item, i) => (
|
{items.map((item, i) => (
|
||||||
<div key={i} style={{
|
<div key={i} style={{
|
||||||
background: 'rgba(17,22,39,0.5)', borderRadius: 10, border: '1px solid rgba(255,255,255,0.08)',
|
background: '#FFFFFF', borderRadius: 10, border: '1px solid rgba(0,0,0,0.08)',
|
||||||
padding: '14px 16px', display: 'flex', alignItems: 'center', gap: 12,
|
padding: '14px 16px', display: 'flex', alignItems: 'center', gap: 12,
|
||||||
}}>
|
}}>
|
||||||
<span style={{ fontSize: 20 }}>{item.icon}</span>
|
<span style={{ fontSize: 20 }}>{item.icon}</span>
|
||||||
@@ -361,7 +361,7 @@ const Models: React.FC = () => {
|
|||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '20px' }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '20px' }}>
|
||||||
<div>
|
<div>
|
||||||
<h2 style={{ margin: 0, fontSize: '20px', fontWeight: 600 }}>大模型配置</h2>
|
<h2 style={{ margin: 0, fontSize: '20px', fontWeight: 600 }}>大模型配置</h2>
|
||||||
<p style={{ margin: '4px 0 0', color: '#8892B0', fontSize: '13px' }}>
|
<p style={{ margin: '4px 0 0', color: '#5A6480', fontSize: '13px' }}>
|
||||||
管理 AI 模型供应商、模型配置和连接测试
|
管理 AI 模型供应商、模型配置和连接测试
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -414,7 +414,7 @@ const Models: React.FC = () => {
|
|||||||
const isExpanded = expandedProvider === provider.id;
|
const isExpanded = expandedProvider === provider.id;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={provider.id} style={{ background: 'rgba(17,22,39,0.5)', borderRadius: '10px', border: '1px solid rgba(255,255,255,0.08)', overflow: 'hidden' }}>
|
<div key={provider.id} style={{ background: '#FFFFFF', borderRadius: '10px', border: '1px solid rgba(0,0,0,0.08)', overflow: 'hidden' }}>
|
||||||
<div style={{ padding: '16px 20px', background: '#F9FAFB', borderBottom: '1px solid #E5E7EB', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
<div style={{ padding: '16px 20px', background: '#F9FAFB', borderBottom: '1px solid #E5E7EB', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||||
<div>
|
<div>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
||||||
@@ -427,7 +427,7 @@ const Models: React.FC = () => {
|
|||||||
)}
|
)}
|
||||||
<span style={{ fontSize: '11px', color: '#9CA3AF' }}>{provModels.length} 个模型</span>
|
<span style={{ fontSize: '11px', color: '#9CA3AF' }}>{provModels.length} 个模型</span>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ fontSize: '12px', color: '#8892B0', marginTop: '4px' }}>
|
<div style={{ fontSize: '12px', color: '#5A6480', marginTop: '4px' }}>
|
||||||
API: {(provider as any).api_base_url || provider.apiBaseUrl || '未配置'}
|
API: {(provider as any).api_base_url || provider.apiBaseUrl || '未配置'}
|
||||||
{(provider as any).api_key || provider.apiKey ? ' ✓ 已配置Key' : ' ✗ 未配置Key'}
|
{(provider as any).api_key || provider.apiKey ? ' ✓ 已配置Key' : ' ✗ 未配置Key'}
|
||||||
</div>
|
</div>
|
||||||
@@ -523,7 +523,7 @@ const Models: React.FC = () => {
|
|||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td style={tdStyle}>
|
<td style={tdStyle}>
|
||||||
<span style={{ fontSize: '12px', color: '#8892B0' }}>
|
<span style={{ fontSize: '12px', color: '#5A6480' }}>
|
||||||
¥{(model.pricePerMillionTokens || 0).toFixed(4)}/M
|
¥{(model.pricePerMillionTokens || 0).toFixed(4)}/M
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
@@ -564,7 +564,7 @@ const Models: React.FC = () => {
|
|||||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||||
}} onClick={() => setExpandedTestResult(null)}>
|
}} onClick={() => setExpandedTestResult(null)}>
|
||||||
<div style={{
|
<div style={{
|
||||||
background: '#111627', borderRadius: 12, padding: 24,
|
background: '#FFFFFF', borderRadius: 12, padding: 24,
|
||||||
maxWidth: 480, width: '90%',
|
maxWidth: 480, width: '90%',
|
||||||
border: `1px solid ${testResults[model.id].success ? 'rgba(34,197,94,0.3)' : 'rgba(239,68,68,0.3)'}`,
|
border: `1px solid ${testResults[model.id].success ? 'rgba(34,197,94,0.3)' : 'rgba(239,68,68,0.3)'}`,
|
||||||
}} onClick={e => e.stopPropagation()}>
|
}} onClick={e => e.stopPropagation()}>
|
||||||
@@ -627,9 +627,9 @@ const Models: React.FC = () => {
|
|||||||
})}
|
})}
|
||||||
|
|
||||||
{providers.length === 0 && (
|
{providers.length === 0 && (
|
||||||
<div style={{ textAlign: 'center', padding: '60px', background: 'rgba(17,22,39,0.5)', borderRadius: '10px', border: '1px solid rgba(255,255,255,0.08)' }}>
|
<div style={{ textAlign: 'center', padding: '60px', background: '#FFFFFF', borderRadius: '10px', border: '1px solid rgba(0,0,0,0.08)' }}>
|
||||||
<div style={{ fontSize: '48px', marginBottom: '16px' }}>🤖</div>
|
<div style={{ fontSize: '48px', marginBottom: '16px' }}>🤖</div>
|
||||||
<p style={{ color: '#8892B0', marginBottom: '16px' }}>暂无模型供应商,点击上方按钮添加</p>
|
<p style={{ color: '#5A6480', marginBottom: '16px' }}>暂无模型供应商,点击上方按钮添加</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -761,6 +761,6 @@ const labelStyle: React.CSSProperties = { display: 'block', marginBottom: '6px',
|
|||||||
const inputStyle: React.CSSProperties = { width: '100%', padding: '8px 12px', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '6px', fontSize: '13px', boxSizing: 'border-box' };
|
const inputStyle: React.CSSProperties = { width: '100%', padding: '8px 12px', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '6px', fontSize: '13px', boxSizing: 'border-box' };
|
||||||
const alertStyle = (bg: string, color: string): React.CSSProperties => ({ background: bg, color, padding: '10px 16px', borderRadius: '6px', fontSize: '13px' });
|
const alertStyle = (bg: string, color: string): React.CSSProperties => ({ background: bg, color, padding: '10px 16px', borderRadius: '6px', fontSize: '13px' });
|
||||||
const modalOverlay: React.CSSProperties = { position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.5)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 1000 };
|
const modalOverlay: React.CSSProperties = { position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.5)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 1000 };
|
||||||
const modalContent: React.CSSProperties = { background: 'rgba(17,22,39,0.5)', borderRadius: '12px', padding: '24px', width: '480px', maxWidth: '90vw', maxHeight: '90vh', overflow: 'auto', boxSizing: 'border-box' };
|
const modalContent: React.CSSProperties = { background: '#FFFFFF', borderRadius: '12px', padding: '24px', width: '480px', maxWidth: '90vw', maxHeight: '90vh', overflow: 'auto', boxSizing: 'border-box' };
|
||||||
|
|
||||||
export default Models;
|
export default Models;
|
||||||
@@ -358,7 +358,7 @@ const Storage: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Tab Switcher */}
|
{/* Tab Switcher */}
|
||||||
<div style={{ display: 'flex', gap: '4px', marginBottom: '20px', borderBottom: '1px solid rgba(255,255,255,0.1)' }}>
|
<div style={{ display: 'flex', gap: '4px', marginBottom: '20px', borderBottom: '1px solid rgba(0,0,0,0.08)' }}>
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveTab('oss')}
|
onClick={() => setActiveTab('oss')}
|
||||||
style={{
|
style={{
|
||||||
@@ -422,19 +422,19 @@ const Storage: React.FC = () => {
|
|||||||
|
|
||||||
<div className="filter-bar" style={{ display: 'flex', gap: '12px', marginBottom: '16px', alignItems: 'center' }}>
|
<div className="filter-bar" style={{ display: 'flex', gap: '12px', marginBottom: '16px', alignItems: 'center' }}>
|
||||||
<select value={filterProvider} onChange={(e) => { setFilterProvider(e.target.value); setPage(1); }}
|
<select value={filterProvider} onChange={(e) => { setFilterProvider(e.target.value); setPage(1); }}
|
||||||
style={{ padding: '8px 12px', borderRadius: '6px', border: '1px solid rgba(255,255,255,0.15)', background: '#111627', color: '#fff', fontSize: '14px' }}>
|
style={{ padding: '8px 12px', borderRadius: '6px', border: '1px solid rgba(0,0,0,0.10)', background: '#FFFFFF', color: '#fff', fontSize: '14px' }}>
|
||||||
<option value="">All Types</option>
|
<option value="">All Types</option>
|
||||||
{Object.entries(PROVIDER_CONFIG).map(([k, v]) => <option key={k} value={k}>{v.label}</option>)}
|
{Object.entries(PROVIDER_CONFIG).map(([k, v]) => <option key={k} value={k}>{v.label}</option>)}
|
||||||
</select>
|
</select>
|
||||||
<input type="text" placeholder="Search..." value={keyword}
|
<input type="text" placeholder="Search..." value={keyword}
|
||||||
onChange={(e) => { setKeyword(e.target.value); setPage(1); }}
|
onChange={(e) => { setKeyword(e.target.value); setPage(1); }}
|
||||||
style={{ padding: '8px 12px', borderRadius: '6px', border: '1px solid rgba(255,255,255,0.15)', background: '#111627', color: '#fff', fontSize: '14px', flex: 1, maxWidth: '400px' }} />
|
style={{ padding: '8px 12px', borderRadius: '6px', border: '1px solid rgba(0,0,0,0.10)', background: '#FFFFFF', color: '#fff', fontSize: '14px', flex: 1, maxWidth: '400px' }} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="table-container" style={{ background: '#111627', borderRadius: '12px', border: '1px solid rgba(255,255,255,0.1)', overflow: 'hidden' }}>
|
<div className="table-container" style={{ background: '#FFFFFF', borderRadius: '12px', border: '1px solid rgba(0,0,0,0.08)', overflow: 'hidden' }}>
|
||||||
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr style={{ background: 'rgba(255,255,255,0.03)', borderBottom: '1px solid rgba(255,255,255,0.1)' }}>
|
<tr style={{ background: 'rgba(255,255,255,0.03)', borderBottom: '1px solid rgba(0,0,0,0.08)' }}>
|
||||||
<th style={{ padding: '12px 16px', textAlign: 'left', fontSize: '13px', color: 'rgba(255,255,255,0.5)', fontWeight: 600 }}>Name</th>
|
<th style={{ padding: '12px 16px', textAlign: 'left', fontSize: '13px', color: 'rgba(255,255,255,0.5)', fontWeight: 600 }}>Name</th>
|
||||||
<th style={{ padding: '12px 16px', textAlign: 'left', fontSize: '13px', color: 'rgba(255,255,255,0.5)', fontWeight: 600 }}>Provider</th>
|
<th style={{ padding: '12px 16px', textAlign: 'left', fontSize: '13px', color: 'rgba(255,255,255,0.5)', fontWeight: 600 }}>Provider</th>
|
||||||
<th style={{ padding: '12px 16px', textAlign: 'left', fontSize: '13px', color: 'rgba(255,255,255,0.5)', fontWeight: 600 }}>Endpoint</th>
|
<th style={{ padding: '12px 16px', textAlign: 'left', fontSize: '13px', color: 'rgba(255,255,255,0.5)', fontWeight: 600 }}>Endpoint</th>
|
||||||
@@ -467,19 +467,19 @@ const Storage: React.FC = () => {
|
|||||||
</td>
|
</td>
|
||||||
<td style={{ padding: '12px 16px', textAlign: 'center' }}>
|
<td style={{ padding: '12px 16px', textAlign: 'center' }}>
|
||||||
{cfg.is_default ? <span style={{ color: '#6C63FF', fontSize: '13px', fontWeight: 600 }}>Default</span> :
|
{cfg.is_default ? <span style={{ color: '#6C63FF', fontSize: '13px', fontWeight: 600 }}>Default</span> :
|
||||||
<button onClick={() => handleSetDefault(cfg)} style={{ padding: '2px 8px', borderRadius: '4px', border: '1px solid rgba(255,255,255,0.15)', background: 'transparent', cursor: 'pointer', fontSize: '12px', color: 'rgba(255,255,255,0.5)' }}>Set Default</button>}
|
<button onClick={() => handleSetDefault(cfg)} style={{ padding: '2px 8px', borderRadius: '4px', border: '1px solid rgba(0,0,0,0.10)', background: 'transparent', cursor: 'pointer', fontSize: '12px', color: 'rgba(255,255,255,0.5)' }}>Set Default</button>}
|
||||||
</td>
|
</td>
|
||||||
<td style={{ padding: '12px 16px', textAlign: 'right' }}>
|
<td style={{ padding: '12px 16px', textAlign: 'right' }}>
|
||||||
<div style={{ display: 'flex', gap: '6px', justifyContent: 'flex-end' }}>
|
<div style={{ display: 'flex', gap: '6px', justifyContent: 'flex-end' }}>
|
||||||
<button onClick={() => handleTestConnection(cfg)} disabled={testingId === cfg.id}
|
<button onClick={() => handleTestConnection(cfg)} disabled={testingId === cfg.id}
|
||||||
style={{ padding: '4px 12px', borderRadius: '6px', border: '1px solid rgba(255,255,255,0.15)', background: 'transparent', cursor: 'pointer', fontSize: '12px', color: 'rgba(255,255,255,0.7)' }}>
|
style={{ padding: '4px 12px', borderRadius: '6px', border: '1px solid rgba(0,0,0,0.10)', background: 'transparent', cursor: 'pointer', fontSize: '12px', color: 'rgba(255,255,255,0.7)' }}>
|
||||||
{testingId === cfg.id ? '...' : 'Test'}
|
{testingId === cfg.id ? '...' : 'Test'}
|
||||||
</button>
|
</button>
|
||||||
<button onClick={() => handleToggle(cfg)}
|
<button onClick={() => handleToggle(cfg)}
|
||||||
style={{ padding: '4px 12px', borderRadius: '6px', border: '1px solid rgba(255,255,255,0.15)', background: 'transparent', cursor: 'pointer', fontSize: '12px', color: cfg.is_enabled ? '#EF4444' : '#10B981' }}>
|
style={{ padding: '4px 12px', borderRadius: '6px', border: '1px solid rgba(0,0,0,0.10)', background: 'transparent', cursor: 'pointer', fontSize: '12px', color: cfg.is_enabled ? '#EF4444' : '#10B981' }}>
|
||||||
{cfg.is_enabled ? 'Disable' : 'Enable'}
|
{cfg.is_enabled ? 'Disable' : 'Enable'}
|
||||||
</button>
|
</button>
|
||||||
<button onClick={() => openEditModal(cfg)} style={{ padding: '4px 12px', borderRadius: '6px', border: '1px solid rgba(255,255,255,0.15)', background: 'transparent', cursor: 'pointer', fontSize: '12px', color: 'rgba(255,255,255,0.7)' }}>Edit</button>
|
<button onClick={() => openEditModal(cfg)} style={{ padding: '4px 12px', borderRadius: '6px', border: '1px solid rgba(0,0,0,0.10)', background: 'transparent', cursor: 'pointer', fontSize: '12px', color: 'rgba(255,255,255,0.7)' }}>Edit</button>
|
||||||
<button onClick={() => handleDelete(cfg)} style={{ padding: '4px 12px', borderRadius: '6px', border: '1px solid rgba(239,68,68,0.2)', background: 'rgba(239,68,68,0.1)', cursor: 'pointer', fontSize: '12px', color: '#EF4444' }}>Delete</button>
|
<button onClick={() => handleDelete(cfg)} style={{ padding: '4px 12px', borderRadius: '6px', border: '1px solid rgba(239,68,68,0.2)', background: 'rgba(239,68,68,0.1)', cursor: 'pointer', fontSize: '12px', color: '#EF4444' }}>Delete</button>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@@ -493,10 +493,10 @@ const Storage: React.FC = () => {
|
|||||||
{total > PAGE_SIZE && (
|
{total > PAGE_SIZE && (
|
||||||
<div style={{ display: 'flex', justifyContent: 'center', gap: '8px', marginTop: '20px' }}>
|
<div style={{ display: 'flex', justifyContent: 'center', gap: '8px', marginTop: '20px' }}>
|
||||||
<button disabled={page <= 1} onClick={() => setPage(p => p - 1)}
|
<button disabled={page <= 1} onClick={() => setPage(p => p - 1)}
|
||||||
style={{ padding: '6px 16px', borderRadius: '6px', border: '1px solid rgba(255,255,255,0.15)', background: 'transparent', cursor: page <= 1 ? 'not-allowed' : 'pointer', opacity: page <= 1 ? 0.5 : 1, color: '#fff' }}>Prev</button>
|
style={{ padding: '6px 16px', borderRadius: '6px', border: '1px solid rgba(0,0,0,0.10)', background: 'transparent', cursor: page <= 1 ? 'not-allowed' : 'pointer', opacity: page <= 1 ? 0.5 : 1, color: '#fff' }}>Prev</button>
|
||||||
<span style={{ padding: '6px 12px', color: 'rgba(255,255,255,0.5)', fontSize: '14px' }}>Page {page} / {Math.ceil(total / PAGE_SIZE)} ({total})</span>
|
<span style={{ padding: '6px 12px', color: 'rgba(255,255,255,0.5)', fontSize: '14px' }}>Page {page} / {Math.ceil(total / PAGE_SIZE)} ({total})</span>
|
||||||
<button disabled={page >= Math.ceil(total / PAGE_SIZE)} onClick={() => setPage(p => p + 1)}
|
<button disabled={page >= Math.ceil(total / PAGE_SIZE)} onClick={() => setPage(p => p + 1)}
|
||||||
style={{ padding: '6px 16px', borderRadius: '6px', border: '1px solid rgba(255,255,255,0.15)', background: 'transparent', cursor: page >= Math.ceil(total / PAGE_SIZE) ? 'not-allowed' : 'pointer', opacity: page >= Math.ceil(total / PAGE_SIZE) ? 0.5 : 1, color: '#fff' }}>Next</button>
|
style={{ padding: '6px 16px', borderRadius: '6px', border: '1px solid rgba(0,0,0,0.10)', background: 'transparent', cursor: page >= Math.ceil(total / PAGE_SIZE) ? 'not-allowed' : 'pointer', opacity: page >= Math.ceil(total / PAGE_SIZE) ? 0.5 : 1, color: '#fff' }}>Next</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
@@ -536,7 +536,7 @@ const Storage: React.FC = () => {
|
|||||||
{/* Tier Defaults */}
|
{/* Tier Defaults */}
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(250px, 1fr))', gap: '16px', marginBottom: '24px' }}>
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(250px, 1fr))', gap: '16px', marginBottom: '24px' }}>
|
||||||
{Object.entries(quotaOverview.default_quotas || {}).map(([level, bytes]) => (
|
{Object.entries(quotaOverview.default_quotas || {}).map(([level, bytes]) => (
|
||||||
<div key={level} style={{ padding: '16px', borderRadius: '12px', background: '#111627', border: '1px solid rgba(255,255,255,0.1)' }}>
|
<div key={level} style={{ padding: '16px', borderRadius: '12px', background: '#FFFFFF', border: '1px solid rgba(0,0,0,0.08)' }}>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '8px' }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '8px' }}>
|
||||||
<span style={{ fontSize: '14px', fontWeight: 600, color: '#fff' }}>
|
<span style={{ fontSize: '14px', fontWeight: 600, color: '#fff' }}>
|
||||||
{(quotaOverview.tier_labels || {})[level] || `Level ${level}`}
|
{(quotaOverview.tier_labels || {})[level] || `Level ${level}`}
|
||||||
@@ -558,7 +558,7 @@ const Storage: React.FC = () => {
|
|||||||
{/* Filter Bar */}
|
{/* Filter Bar */}
|
||||||
<div className="filter-bar" style={{ display: 'flex', gap: '12px', marginBottom: '16px', alignItems: 'center' }}>
|
<div className="filter-bar" style={{ display: 'flex', gap: '12px', marginBottom: '16px', alignItems: 'center' }}>
|
||||||
<select value={quotaLevelFilter} onChange={(e) => { setQuotaLevelFilter(e.target.value === '' ? '' : parseInt(e.target.value)); setQuotaPage(1); }}
|
<select value={quotaLevelFilter} onChange={(e) => { setQuotaLevelFilter(e.target.value === '' ? '' : parseInt(e.target.value)); setQuotaPage(1); }}
|
||||||
style={{ padding: '8px 12px', borderRadius: '6px', border: '1px solid rgba(255,255,255,0.15)', background: '#111627', color: '#fff', fontSize: '14px' }}>
|
style={{ padding: '8px 12px', borderRadius: '6px', border: '1px solid rgba(0,0,0,0.10)', background: '#FFFFFF', color: '#fff', fontSize: '14px' }}>
|
||||||
<option value="">All Tiers</option>
|
<option value="">All Tiers</option>
|
||||||
<option value="0">Free</option>
|
<option value="0">Free</option>
|
||||||
<option value="2">Pro</option>
|
<option value="2">Pro</option>
|
||||||
@@ -566,14 +566,14 @@ const Storage: React.FC = () => {
|
|||||||
</select>
|
</select>
|
||||||
<input type="text" placeholder="Search username/email..." value={quotaKeyword}
|
<input type="text" placeholder="Search username/email..." value={quotaKeyword}
|
||||||
onChange={(e) => { setQuotaKeyword(e.target.value); setQuotaPage(1); }}
|
onChange={(e) => { setQuotaKeyword(e.target.value); setQuotaPage(1); }}
|
||||||
style={{ padding: '8px 12px', borderRadius: '6px', border: '1px solid rgba(255,255,255,0.15)', background: '#111627', color: '#fff', fontSize: '14px', flex: 1, maxWidth: '400px' }} />
|
style={{ padding: '8px 12px', borderRadius: '6px', border: '1px solid rgba(0,0,0,0.10)', background: '#FFFFFF', color: '#fff', fontSize: '14px', flex: 1, maxWidth: '400px' }} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Quota Users Table */}
|
{/* Quota Users Table */}
|
||||||
<div className="table-container" style={{ background: '#111627', borderRadius: '12px', border: '1px solid rgba(255,255,255,0.1)', overflow: 'hidden' }}>
|
<div className="table-container" style={{ background: '#FFFFFF', borderRadius: '12px', border: '1px solid rgba(0,0,0,0.08)', overflow: 'hidden' }}>
|
||||||
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr style={{ background: 'rgba(255,255,255,0.03)', borderBottom: '1px solid rgba(255,255,255,0.1)' }}>
|
<tr style={{ background: 'rgba(255,255,255,0.03)', borderBottom: '1px solid rgba(0,0,0,0.08)' }}>
|
||||||
<th style={{ padding: '12px 16px', textAlign: 'left', fontSize: '13px', color: 'rgba(255,255,255,0.5)', fontWeight: 600 }}>User</th>
|
<th style={{ padding: '12px 16px', textAlign: 'left', fontSize: '13px', color: 'rgba(255,255,255,0.5)', fontWeight: 600 }}>User</th>
|
||||||
<th style={{ padding: '12px 16px', textAlign: 'center', fontSize: '13px', color: 'rgba(255,255,255,0.5)', fontWeight: 600 }}>Tier</th>
|
<th style={{ padding: '12px 16px', textAlign: 'center', fontSize: '13px', color: 'rgba(255,255,255,0.5)', fontWeight: 600 }}>Tier</th>
|
||||||
<th style={{ padding: '12px 16px', textAlign: 'left', fontSize: '13px', color: 'rgba(255,255,255,0.5)', fontWeight: 600, width: '300px' }}>Usage</th>
|
<th style={{ padding: '12px 16px', textAlign: 'left', fontSize: '13px', color: 'rgba(255,255,255,0.5)', fontWeight: 600, width: '300px' }}>Usage</th>
|
||||||
@@ -626,9 +626,9 @@ const Storage: React.FC = () => {
|
|||||||
</td>
|
</td>
|
||||||
<td style={{ padding: '12px 16px', textAlign: 'right' }}>
|
<td style={{ padding: '12px 16px', textAlign: 'right' }}>
|
||||||
<div style={{ display: 'flex', gap: '6px', justifyContent: 'flex-end' }}>
|
<div style={{ display: 'flex', gap: '6px', justifyContent: 'flex-end' }}>
|
||||||
<button onClick={() => handleViewDetail(u.user_id)} style={{ padding: '4px 12px', borderRadius: '6px', border: '1px solid rgba(255,255,255,0.15)', background: 'transparent', cursor: 'pointer', fontSize: '12px', color: 'rgba(255,255,255,0.7)' }}>Detail</button>
|
<button onClick={() => handleViewDetail(u.user_id)} style={{ padding: '4px 12px', borderRadius: '6px', border: '1px solid rgba(0,0,0,0.10)', background: 'transparent', cursor: 'pointer', fontSize: '12px', color: 'rgba(255,255,255,0.7)' }}>Detail</button>
|
||||||
<button onClick={() => openAdjustModal(u)} style={{ padding: '4px 12px', borderRadius: '6px', border: '1px solid rgba(108,99,255,0.3)', background: 'rgba(108,99,255,0.1)', cursor: 'pointer', fontSize: '12px', color: '#6C63FF' }}>Adjust</button>
|
<button onClick={() => openAdjustModal(u)} style={{ padding: '4px 12px', borderRadius: '6px', border: '1px solid rgba(108,99,255,0.3)', background: 'rgba(108,99,255,0.1)', cursor: 'pointer', fontSize: '12px', color: '#6C63FF' }}>Adjust</button>
|
||||||
<button onClick={() => handleRecalcUser(u.user_id)} style={{ padding: '4px 12px', borderRadius: '6px', border: '1px solid rgba(255,255,255,0.15)', background: 'transparent', cursor: 'pointer', fontSize: '12px', color: 'rgba(255,255,255,0.7)' }}>Recalc</button>
|
<button onClick={() => handleRecalcUser(u.user_id)} style={{ padding: '4px 12px', borderRadius: '6px', border: '1px solid rgba(0,0,0,0.10)', background: 'transparent', cursor: 'pointer', fontSize: '12px', color: 'rgba(255,255,255,0.7)' }}>Recalc</button>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -641,10 +641,10 @@ const Storage: React.FC = () => {
|
|||||||
{quotaTotal > PAGE_SIZE && (
|
{quotaTotal > PAGE_SIZE && (
|
||||||
<div style={{ display: 'flex', justifyContent: 'center', gap: '8px', marginTop: '20px' }}>
|
<div style={{ display: 'flex', justifyContent: 'center', gap: '8px', marginTop: '20px' }}>
|
||||||
<button disabled={quotaPage <= 1} onClick={() => setQuotaPage(p => p - 1)}
|
<button disabled={quotaPage <= 1} onClick={() => setQuotaPage(p => p - 1)}
|
||||||
style={{ padding: '6px 16px', borderRadius: '6px', border: '1px solid rgba(255,255,255,0.15)', background: 'transparent', cursor: quotaPage <= 1 ? 'not-allowed' : 'pointer', opacity: quotaPage <= 1 ? 0.5 : 1, color: '#fff' }}>Prev</button>
|
style={{ padding: '6px 16px', borderRadius: '6px', border: '1px solid rgba(0,0,0,0.10)', background: 'transparent', cursor: quotaPage <= 1 ? 'not-allowed' : 'pointer', opacity: quotaPage <= 1 ? 0.5 : 1, color: '#fff' }}>Prev</button>
|
||||||
<span style={{ padding: '6px 12px', color: 'rgba(255,255,255,0.5)', fontSize: '14px' }}>Page {quotaPage} / {Math.ceil(quotaTotal / PAGE_SIZE)} ({quotaTotal})</span>
|
<span style={{ padding: '6px 12px', color: 'rgba(255,255,255,0.5)', fontSize: '14px' }}>Page {quotaPage} / {Math.ceil(quotaTotal / PAGE_SIZE)} ({quotaTotal})</span>
|
||||||
<button disabled={quotaPage >= Math.ceil(quotaTotal / PAGE_SIZE)} onClick={() => setQuotaPage(p => p + 1)}
|
<button disabled={quotaPage >= Math.ceil(quotaTotal / PAGE_SIZE)} onClick={() => setQuotaPage(p => p + 1)}
|
||||||
style={{ padding: '6px 16px', borderRadius: '6px', border: '1px solid rgba(255,255,255,0.15)', background: 'transparent', cursor: quotaPage >= Math.ceil(quotaTotal / PAGE_SIZE) ? 'not-allowed' : 'pointer', opacity: quotaPage >= Math.ceil(quotaTotal / PAGE_SIZE) ? 0.5 : 1, color: '#fff' }}>Next</button>
|
style={{ padding: '6px 16px', borderRadius: '6px', border: '1px solid rgba(0,0,0,0.10)', background: 'transparent', cursor: quotaPage >= Math.ceil(quotaTotal / PAGE_SIZE) ? 'not-allowed' : 'pointer', opacity: quotaPage >= Math.ceil(quotaTotal / PAGE_SIZE) ? 0.5 : 1, color: '#fff' }}>Next</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
@@ -660,9 +660,9 @@ const Storage: React.FC = () => {
|
|||||||
justifyContent: 'center', zIndex: 1000,
|
justifyContent: 'center', zIndex: 1000,
|
||||||
}}>
|
}}>
|
||||||
<div className="modal-content" style={{
|
<div className="modal-content" style={{
|
||||||
background: '#111627', borderRadius: '12px', padding: '24px',
|
background: '#FFFFFF', borderRadius: '12px', padding: '24px',
|
||||||
width: '560px', maxHeight: '90vh', overflow: 'auto',
|
width: '560px', maxHeight: '90vh', overflow: 'auto',
|
||||||
boxShadow: '0 20px 60px rgba(0,0,0,0.5)', border: '1px solid rgba(255,255,255,0.1)',
|
boxShadow: '0 20px 60px rgba(0,0,0,0.5)', border: '1px solid rgba(0,0,0,0.08)',
|
||||||
}}>
|
}}>
|
||||||
<h3 style={{ margin: '0 0 20px', fontSize: '18px', color: '#fff' }}>
|
<h3 style={{ margin: '0 0 20px', fontSize: '18px', color: '#fff' }}>
|
||||||
{editingConfig ? 'Edit Storage Config' : 'Add Storage Config'}
|
{editingConfig ? 'Edit Storage Config' : 'Add Storage Config'}
|
||||||
@@ -672,12 +672,12 @@ const Storage: React.FC = () => {
|
|||||||
<label style={{ display: 'block', marginBottom: '4px', fontSize: '13px', color: 'rgba(255,255,255,0.7)', fontWeight: 500 }}>Name *</label>
|
<label style={{ display: 'block', marginBottom: '4px', fontSize: '13px', color: 'rgba(255,255,255,0.7)', fontWeight: 500 }}>Name *</label>
|
||||||
<input type="text" value={formData.name} onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
<input type="text" value={formData.name} onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||||
placeholder="e.g. Production MinIO"
|
placeholder="e.g. Production MinIO"
|
||||||
style={{ width: '100%', padding: '8px 12px', borderRadius: '6px', border: '1px solid rgba(255,255,255,0.15)', background: '#0A0E1A', color: '#fff', fontSize: '14px', boxSizing: 'border-box' }} />
|
style={{ width: '100%', padding: '8px 12px', borderRadius: '6px', border: '1px solid rgba(0,0,0,0.10)', background: '#FFFFFF', color: '#fff', fontSize: '14px', boxSizing: 'border-box' }} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label style={{ display: 'block', marginBottom: '4px', fontSize: '13px', color: 'rgba(255,255,255,0.7)', fontWeight: 500 }}>Provider</label>
|
<label style={{ display: 'block', marginBottom: '4px', fontSize: '13px', color: 'rgba(255,255,255,0.7)', fontWeight: 500 }}>Provider</label>
|
||||||
<select value={formData.provider} onChange={(e) => setFormData({ ...formData, provider: e.target.value })}
|
<select value={formData.provider} onChange={(e) => setFormData({ ...formData, provider: e.target.value })}
|
||||||
style={{ width: '100%', padding: '8px 12px', borderRadius: '6px', border: '1px solid rgba(255,255,255,0.15)', background: '#0A0E1A', color: '#fff', fontSize: '14px', boxSizing: 'border-box' }}>
|
style={{ width: '100%', padding: '8px 12px', borderRadius: '6px', border: '1px solid rgba(0,0,0,0.10)', background: '#FFFFFF', color: '#fff', fontSize: '14px', boxSizing: 'border-box' }}>
|
||||||
<option value="minio">MinIO</option>
|
<option value="minio">MinIO</option>
|
||||||
<option value="aliyun_oss">Aliyun OSS</option>
|
<option value="aliyun_oss">Aliyun OSS</option>
|
||||||
<option value="aws_s3">AWS S3</option>
|
<option value="aws_s3">AWS S3</option>
|
||||||
@@ -687,20 +687,20 @@ const Storage: React.FC = () => {
|
|||||||
<label style={{ display: 'block', marginBottom: '4px', fontSize: '13px', color: 'rgba(255,255,255,0.7)', fontWeight: 500 }}>Endpoint *</label>
|
<label style={{ display: 'block', marginBottom: '4px', fontSize: '13px', color: 'rgba(255,255,255,0.7)', fontWeight: 500 }}>Endpoint *</label>
|
||||||
<input type="text" value={formData.endpoint} onChange={(e) => setFormData({ ...formData, endpoint: e.target.value })}
|
<input type="text" value={formData.endpoint} onChange={(e) => setFormData({ ...formData, endpoint: e.target.value })}
|
||||||
placeholder={formData.provider === 'minio' ? 'http://localhost:9000' : 'https://oss-cn-hangzhou.aliyuncs.com'}
|
placeholder={formData.provider === 'minio' ? 'http://localhost:9000' : 'https://oss-cn-hangzhou.aliyuncs.com'}
|
||||||
style={{ width: '100%', padding: '8px 12px', borderRadius: '6px', border: '1px solid rgba(255,255,255,0.15)', background: '#0A0E1A', color: '#fff', fontSize: '14px', boxSizing: 'border-box' }} />
|
style={{ width: '100%', padding: '8px 12px', borderRadius: '6px', border: '1px solid rgba(0,0,0,0.10)', background: '#FFFFFF', color: '#fff', fontSize: '14px', boxSizing: 'border-box' }} />
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px' }}>
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px' }}>
|
||||||
<div>
|
<div>
|
||||||
<label style={{ display: 'block', marginBottom: '4px', fontSize: '13px', color: 'rgba(255,255,255,0.7)', fontWeight: 500 }}>Access Key {!editingConfig ? '*' : ''}</label>
|
<label style={{ display: 'block', marginBottom: '4px', fontSize: '13px', color: 'rgba(255,255,255,0.7)', fontWeight: 500 }}>Access Key {!editingConfig ? '*' : ''}</label>
|
||||||
<input type="text" value={formData.access_key} onChange={(e) => setFormData({ ...formData, access_key: e.target.value })}
|
<input type="text" value={formData.access_key} onChange={(e) => setFormData({ ...formData, access_key: e.target.value })}
|
||||||
placeholder={editingConfig ? 'Leave empty to keep' : 'Enter Access Key'}
|
placeholder={editingConfig ? 'Leave empty to keep' : 'Enter Access Key'}
|
||||||
style={{ width: '100%', padding: '8px 12px', borderRadius: '6px', border: '1px solid rgba(255,255,255,0.15)', background: '#0A0E1A', color: '#fff', fontSize: '14px', boxSizing: 'border-box' }} />
|
style={{ width: '100%', padding: '8px 12px', borderRadius: '6px', border: '1px solid rgba(0,0,0,0.10)', background: '#FFFFFF', color: '#fff', fontSize: '14px', boxSizing: 'border-box' }} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label style={{ display: 'block', marginBottom: '4px', fontSize: '13px', color: 'rgba(255,255,255,0.7)', fontWeight: 500 }}>Secret Key {!editingConfig ? '*' : ''}</label>
|
<label style={{ display: 'block', marginBottom: '4px', fontSize: '13px', color: 'rgba(255,255,255,0.7)', fontWeight: 500 }}>Secret Key {!editingConfig ? '*' : ''}</label>
|
||||||
<input type="password" value={formData.secret_key} onChange={(e) => setFormData({ ...formData, secret_key: e.target.value })}
|
<input type="password" value={formData.secret_key} onChange={(e) => setFormData({ ...formData, secret_key: e.target.value })}
|
||||||
placeholder={editingConfig ? 'Leave empty to keep' : 'Enter Secret Key'}
|
placeholder={editingConfig ? 'Leave empty to keep' : 'Enter Secret Key'}
|
||||||
style={{ width: '100%', padding: '8px 12px', borderRadius: '6px', border: '1px solid rgba(255,255,255,0.15)', background: '#0A0E1A', color: '#fff', fontSize: '14px', boxSizing: 'border-box' }} />
|
style={{ width: '100%', padding: '8px 12px', borderRadius: '6px', border: '1px solid rgba(0,0,0,0.10)', background: '#FFFFFF', color: '#fff', fontSize: '14px', boxSizing: 'border-box' }} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px' }}>
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px' }}>
|
||||||
@@ -708,20 +708,20 @@ const Storage: React.FC = () => {
|
|||||||
<label style={{ display: 'block', marginBottom: '4px', fontSize: '13px', color: 'rgba(255,255,255,0.7)', fontWeight: 500 }}>Bucket *</label>
|
<label style={{ display: 'block', marginBottom: '4px', fontSize: '13px', color: 'rgba(255,255,255,0.7)', fontWeight: 500 }}>Bucket *</label>
|
||||||
<input type="text" value={formData.bucket} onChange={(e) => setFormData({ ...formData, bucket: e.target.value })}
|
<input type="text" value={formData.bucket} onChange={(e) => setFormData({ ...formData, bucket: e.target.value })}
|
||||||
placeholder="my-bucket"
|
placeholder="my-bucket"
|
||||||
style={{ width: '100%', padding: '8px 12px', borderRadius: '6px', border: '1px solid rgba(255,255,255,0.15)', background: '#0A0E1A', color: '#fff', fontSize: '14px', boxSizing: 'border-box' }} />
|
style={{ width: '100%', padding: '8px 12px', borderRadius: '6px', border: '1px solid rgba(0,0,0,0.10)', background: '#FFFFFF', color: '#fff', fontSize: '14px', boxSizing: 'border-box' }} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label style={{ display: 'block', marginBottom: '4px', fontSize: '13px', color: 'rgba(255,255,255,0.7)', fontWeight: 500 }}>Region</label>
|
<label style={{ display: 'block', marginBottom: '4px', fontSize: '13px', color: 'rgba(255,255,255,0.7)', fontWeight: 500 }}>Region</label>
|
||||||
<input type="text" value={formData.region} onChange={(e) => setFormData({ ...formData, region: e.target.value })}
|
<input type="text" value={formData.region} onChange={(e) => setFormData({ ...formData, region: e.target.value })}
|
||||||
placeholder="us-east-1"
|
placeholder="us-east-1"
|
||||||
style={{ width: '100%', padding: '8px 12px', borderRadius: '6px', border: '1px solid rgba(255,255,255,0.15)', background: '#0A0E1A', color: '#fff', fontSize: '14px', boxSizing: 'border-box' }} />
|
style={{ width: '100%', padding: '8px 12px', borderRadius: '6px', border: '1px solid rgba(0,0,0,0.10)', background: '#FFFFFF', color: '#fff', fontSize: '14px', boxSizing: 'border-box' }} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label style={{ display: 'block', marginBottom: '4px', fontSize: '13px', color: 'rgba(255,255,255,0.7)', fontWeight: 500 }}>Base Path</label>
|
<label style={{ display: 'block', marginBottom: '4px', fontSize: '13px', color: 'rgba(255,255,255,0.7)', fontWeight: 500 }}>Base Path</label>
|
||||||
<input type="text" value={formData.base_path} onChange={(e) => setFormData({ ...formData, base_path: e.target.value })}
|
<input type="text" value={formData.base_path} onChange={(e) => setFormData({ ...formData, base_path: e.target.value })}
|
||||||
placeholder="aipaaS/uploads/"
|
placeholder="aipaaS/uploads/"
|
||||||
style={{ width: '100%', padding: '8px 12px', borderRadius: '6px', border: '1px solid rgba(255,255,255,0.15)', background: '#0A0E1A', color: '#fff', fontSize: '14px', boxSizing: 'border-box' }} />
|
style={{ width: '100%', padding: '8px 12px', borderRadius: '6px', border: '1px solid rgba(0,0,0,0.10)', background: '#FFFFFF', color: '#fff', fontSize: '14px', boxSizing: 'border-box' }} />
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', gap: '20px' }}>
|
<div style={{ display: 'flex', gap: '20px' }}>
|
||||||
<label style={{ display: 'flex', alignItems: 'center', gap: '6px', fontSize: '14px', cursor: 'pointer', color: 'rgba(255,255,255,0.8)' }}>
|
<label style={{ display: 'flex', alignItems: 'center', gap: '6px', fontSize: '14px', cursor: 'pointer', color: 'rgba(255,255,255,0.8)' }}>
|
||||||
@@ -735,11 +735,11 @@ const Storage: React.FC = () => {
|
|||||||
<label style={{ display: 'block', marginBottom: '4px', fontSize: '13px', color: 'rgba(255,255,255,0.7)', fontWeight: 500 }}>Extra Config (JSON, optional)</label>
|
<label style={{ display: 'block', marginBottom: '4px', fontSize: '13px', color: 'rgba(255,255,255,0.7)', fontWeight: 500 }}>Extra Config (JSON, optional)</label>
|
||||||
<textarea value={formData.config_str} onChange={(e) => setFormData({ ...formData, config_str: e.target.value })}
|
<textarea value={formData.config_str} onChange={(e) => setFormData({ ...formData, config_str: e.target.value })}
|
||||||
placeholder='{"use_ssl": true, "timeout": 30}' rows={3}
|
placeholder='{"use_ssl": true, "timeout": 30}' rows={3}
|
||||||
style={{ width: '100%', padding: '8px 12px', borderRadius: '6px', border: '1px solid rgba(255,255,255,0.15)', background: '#0A0E1A', color: '#fff', fontSize: '13px', fontFamily: 'monospace', boxSizing: 'border-box', resize: 'vertical' }} />
|
style={{ width: '100%', padding: '8px 12px', borderRadius: '6px', border: '1px solid rgba(0,0,0,0.10)', background: '#FFFFFF', color: '#fff', fontSize: '13px', fontFamily: 'monospace', boxSizing: 'border-box', resize: 'vertical' }} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: '10px', marginTop: '20px' }}>
|
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: '10px', marginTop: '20px' }}>
|
||||||
<button onClick={() => setModalVisible(false)} style={{ padding: '8px 20px', borderRadius: '6px', border: '1px solid rgba(255,255,255,0.15)', background: 'transparent', cursor: 'pointer', fontSize: '14px', color: 'rgba(255,255,255,0.7)' }}>Cancel</button>
|
<button onClick={() => setModalVisible(false)} style={{ padding: '8px 20px', borderRadius: '6px', border: '1px solid rgba(0,0,0,0.10)', background: 'transparent', cursor: 'pointer', fontSize: '14px', color: 'rgba(255,255,255,0.7)' }}>Cancel</button>
|
||||||
<button onClick={handleSubmit} disabled={submitting} style={{ padding: '8px 20px', borderRadius: '6px', border: 'none', background: '#6C63FF', cursor: submitting ? 'not-allowed' : 'pointer', fontSize: '14px', color: '#fff', opacity: submitting ? 0.7 : 1 }}>
|
<button onClick={handleSubmit} disabled={submitting} style={{ padding: '8px 20px', borderRadius: '6px', border: 'none', background: '#6C63FF', cursor: submitting ? 'not-allowed' : 'pointer', fontSize: '14px', color: '#fff', opacity: submitting ? 0.7 : 1 }}>
|
||||||
{submitting ? 'Saving...' : (editingConfig ? 'Save' : 'Create')}
|
{submitting ? 'Saving...' : (editingConfig ? 'Save' : 'Create')}
|
||||||
</button>
|
</button>
|
||||||
@@ -758,9 +758,9 @@ const Storage: React.FC = () => {
|
|||||||
justifyContent: 'center', zIndex: 1000,
|
justifyContent: 'center', zIndex: 1000,
|
||||||
}}>
|
}}>
|
||||||
<div className="modal-content" style={{
|
<div className="modal-content" style={{
|
||||||
background: '#111627', borderRadius: '12px', padding: '24px',
|
background: '#FFFFFF', borderRadius: '12px', padding: '24px',
|
||||||
width: '520px', maxHeight: '90vh', overflow: 'auto',
|
width: '520px', maxHeight: '90vh', overflow: 'auto',
|
||||||
boxShadow: '0 20px 60px rgba(0,0,0,0.5)', border: '1px solid rgba(255,255,255,0.1)',
|
boxShadow: '0 20px 60px rgba(0,0,0,0.5)', border: '1px solid rgba(0,0,0,0.08)',
|
||||||
}}>
|
}}>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '20px' }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '20px' }}>
|
||||||
<h3 style={{ margin: 0, fontSize: '18px', color: '#fff' }}>Storage Detail</h3>
|
<h3 style={{ margin: 0, fontSize: '18px', color: '#fff' }}>Storage Detail</h3>
|
||||||
@@ -819,7 +819,7 @@ const Storage: React.FC = () => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: '20px' }}>
|
<div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: '20px' }}>
|
||||||
<button onClick={() => setQuotaDetail(null)} style={{ padding: '8px 20px', borderRadius: '6px', border: '1px solid rgba(255,255,255,0.15)', background: 'transparent', cursor: 'pointer', fontSize: '14px', color: 'rgba(255,255,255,0.7)' }}>Close</button>
|
<button onClick={() => setQuotaDetail(null)} style={{ padding: '8px 20px', borderRadius: '6px', border: '1px solid rgba(0,0,0,0.10)', background: 'transparent', cursor: 'pointer', fontSize: '14px', color: 'rgba(255,255,255,0.7)' }}>Close</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -835,9 +835,9 @@ const Storage: React.FC = () => {
|
|||||||
justifyContent: 'center', zIndex: 1000,
|
justifyContent: 'center', zIndex: 1000,
|
||||||
}}>
|
}}>
|
||||||
<div className="modal-content" style={{
|
<div className="modal-content" style={{
|
||||||
background: '#111627', borderRadius: '12px', padding: '24px',
|
background: '#FFFFFF', borderRadius: '12px', padding: '24px',
|
||||||
width: '420px', boxShadow: '0 20px 60px rgba(0,0,0,0.5)',
|
width: '420px', boxShadow: '0 20px 60px rgba(0,0,0,0.5)',
|
||||||
border: '1px solid rgba(255,255,255,0.1)',
|
border: '1px solid rgba(0,0,0,0.08)',
|
||||||
}}>
|
}}>
|
||||||
<h3 style={{ margin: '0 0 20px', fontSize: '18px', color: '#fff' }}>Adjust Quota</h3>
|
<h3 style={{ margin: '0 0 20px', fontSize: '18px', color: '#fff' }}>Adjust Quota</h3>
|
||||||
<div style={{ marginBottom: '16px' }}>
|
<div style={{ marginBottom: '16px' }}>
|
||||||
@@ -851,9 +851,9 @@ const Storage: React.FC = () => {
|
|||||||
<div style={{ display: 'flex', gap: '8px' }}>
|
<div style={{ display: 'flex', gap: '8px' }}>
|
||||||
<input type="number" value={adjustValue} onChange={(e) => setAdjustValue(e.target.value)}
|
<input type="number" value={adjustValue} onChange={(e) => setAdjustValue(e.target.value)}
|
||||||
step="0.1" min="0"
|
step="0.1" min="0"
|
||||||
style={{ flex: 1, padding: '8px 12px', borderRadius: '6px', border: '1px solid rgba(255,255,255,0.15)', background: '#0A0E1A', color: '#fff', fontSize: '14px' }} />
|
style={{ flex: 1, padding: '8px 12px', borderRadius: '6px', border: '1px solid rgba(0,0,0,0.10)', background: '#FFFFFF', color: '#fff', fontSize: '14px' }} />
|
||||||
<select value={adjustUnit} onChange={(e) => setAdjustUnit(e.target.value as 'GB' | 'MB')}
|
<select value={adjustUnit} onChange={(e) => setAdjustUnit(e.target.value as 'GB' | 'MB')}
|
||||||
style={{ padding: '8px 12px', borderRadius: '6px', border: '1px solid rgba(255,255,255,0.15)', background: '#0A0E1A', color: '#fff', fontSize: '14px' }}>
|
style={{ padding: '8px 12px', borderRadius: '6px', border: '1px solid rgba(0,0,0,0.10)', background: '#FFFFFF', color: '#fff', fontSize: '14px' }}>
|
||||||
<option value="GB">GB</option>
|
<option value="GB">GB</option>
|
||||||
<option value="MB">MB</option>
|
<option value="MB">MB</option>
|
||||||
</select>
|
</select>
|
||||||
@@ -863,7 +863,7 @@ const Storage: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: '10px' }}>
|
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: '10px' }}>
|
||||||
<button onClick={() => setAdjustModal(null)} style={{ padding: '8px 20px', borderRadius: '6px', border: '1px solid rgba(255,255,255,0.15)', background: 'transparent', cursor: 'pointer', fontSize: '14px', color: 'rgba(255,255,255,0.7)' }}>Cancel</button>
|
<button onClick={() => setAdjustModal(null)} style={{ padding: '8px 20px', borderRadius: '6px', border: '1px solid rgba(0,0,0,0.10)', background: 'transparent', cursor: 'pointer', fontSize: '14px', color: 'rgba(255,255,255,0.7)' }}>Cancel</button>
|
||||||
<button onClick={handleAdjustSubmit} disabled={adjusting}
|
<button onClick={handleAdjustSubmit} disabled={adjusting}
|
||||||
style={{ padding: '8px 20px', borderRadius: '6px', border: 'none', background: '#6C63FF', cursor: adjusting ? 'not-allowed' : 'pointer', fontSize: '14px', color: '#fff', opacity: adjusting ? 0.7 : 1 }}>
|
style={{ padding: '8px 20px', borderRadius: '6px', border: 'none', background: '#6C63FF', cursor: adjusting ? 'not-allowed' : 'pointer', fontSize: '14px', color: '#fff', opacity: adjusting ? 0.7 : 1 }}>
|
||||||
{adjusting ? 'Saving...' : 'Confirm'}
|
{adjusting ? 'Saving...' : 'Confirm'}
|
||||||
|
|||||||
@@ -91,7 +91,7 @@
|
|||||||
width: 120px;
|
width: 120px;
|
||||||
height: 120px;
|
height: 120px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: rgba(255, 255, 255, 0.08);
|
background: rgba(0, 0, 0, 0.06);
|
||||||
top: -30px;
|
top: -30px;
|
||||||
right: -30px;
|
right: -30px;
|
||||||
}
|
}
|
||||||
@@ -102,7 +102,7 @@
|
|||||||
width: 80px;
|
width: 80px;
|
||||||
height: 80px;
|
height: 80px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: rgba(255, 255, 255, 0.06);
|
background: rgba(0, 0, 0, 0.04);
|
||||||
bottom: -20px;
|
bottom: -20px;
|
||||||
left: -20px;
|
left: -20px;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user