fix: comprehensive dark theme removal - all admin pages, storage, models, achievements, developer tools, front CSS

This commit is contained in:
qyyfw committed 2026-07-07 23:05:55 +08:00
1 parent a6522d5069
commit c729f61831
5 files changed
+82 -82

No files matched your search

+24 -24
View File
@@ -269,65 +269,65 @@ const Achievement: React.FC = () => {
return (
<div className="admin-page" style={{ padding: '24px' }}>
<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:hover { color: rgba(255,255,255,0.8); }
.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-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 .label { font-size: 12px; color: rgba(255,255,255,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 { 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(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: #1A1F36; }
.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-track { flex: 1; height: 8px; background: rgba(255,255,255,0.06); border-radius: 4px; overflow: hidden; }
.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(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-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-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:hover { border-color: rgba(108,99,255,0.3); background: rgba(255,255,255,0.05); }
.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(0,0,0,0.04); }
.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-name { font-size: 16px; font-weight: 600; color: #fff; 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-name { font-size: 16px; font-weight: 600; color: #1A1F36; margin-bottom: 4px; }
.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-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.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-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-danger { background: rgba(255,107,107,0.15); color: #FF6B6B; }
.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: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:hover { background: rgba(255,255,255,0.1); }
.ach-btn-ghost { background: rgba(0,0,0,0.06); color: rgba(0,0,0,0.6); }
.ach-btn-ghost:hover { background: rgba(0,0,0,0.10); }
.ach-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.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-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 h2 { font-size: 20px; color: #fff; margin-bottom: 20px; }
.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: #1A1F36; margin-bottom: 20px; }
.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 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 select option { background: #1A1A2E; }
.ach-field-row { display: flex; gap: 12px; }
.ach-field-row .ach-field { flex: 1; }
.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 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-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.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.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; }
`}</style>
@@ -381,7 +381,7 @@ const Achievement: React.FC = () => {
</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>
{Object.entries(stats.by_category).map(([cat, count]) => {
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-meta">
<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}
</span>
<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.achievement_count || 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') : '-'}
</td>
</tr>
+7 -7
View File
@@ -221,7 +221,7 @@ export default function AdminDeveloperTools() {
</div>
{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>
<span style={{ color: testResult.status === 'success' ? '#52c41a' : '#ff4d4f' }}>{testResult.status}</span>
{testResult.response_code && <span> | HTTP {testResult.response_code}</span>}
@@ -307,11 +307,11 @@ export default function AdminDeveloperTools() {
{/* Supported Events Info */}
{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>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))', gap: '8px' }}>
{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>
<div style={{ fontSize: '12px', color: 'rgba(255,255,255,0.45)', marginTop: '2px' }}>{ev.desc}</div>
</div>
@@ -339,7 +339,7 @@ export default function AdminDeveloperTools() {
<label>Events (leave empty for all)</label>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px', marginTop: '8px' }}>
{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 => {
if (e.target.checked) setWebhookForm({ ...webhookForm, events: [...webhookForm.events, ev.value] });
else setWebhookForm({ ...webhookForm, events: webhookForm.events.filter(x => x !== ev.value) });
@@ -372,7 +372,7 @@ export default function AdminDeveloperTools() {
</div>
{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 */}
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: '16px' }}>
<div>
@@ -442,7 +442,7 @@ export default function AdminDeveloperTools() {
{/* Section Header */}
<div
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' }}>
<span style={{ fontSize: '18px' }}>
@@ -462,7 +462,7 @@ export default function AdminDeveloperTools() {
const epKey = `${section.section}-${ep.path}`;
const isEpExpanded = expandedEndpoint === epKey;
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
onClick={() => setExpandedEndpoint(isEpExpanded ? null : epKey)}
style={{ padding: '10px 16px', cursor: 'pointer', display: 'flex', alignItems: 'center', gap: '12px' }}
+9 -9
View File
@@ -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 }}>
{items.map((item, i) => (
<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,
}}>
<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>
<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 模型供应商、模型配置和连接测试
</p>
</div>
@@ -414,7 +414,7 @@ const Models: React.FC = () => {
const isExpanded = expandedProvider === provider.id;
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>
<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>
</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 || '未配置'}
{(provider as any).api_key || provider.apiKey ? ' ✓ 已配置Key' : ' ✗ 未配置Key'}
</div>
@@ -523,7 +523,7 @@ const Models: React.FC = () => {
</span>
</td>
<td style={tdStyle}>
<span style={{ fontSize: '12px', color: '#8892B0' }}>
<span style={{ fontSize: '12px', color: '#5A6480' }}>
¥{(model.pricePerMillionTokens || 0).toFixed(4)}/M
</span>
</td>
@@ -564,7 +564,7 @@ const Models: React.FC = () => {
display: 'flex', alignItems: 'center', justifyContent: 'center',
}} onClick={() => setExpandedTestResult(null)}>
<div style={{
background: '#111627', borderRadius: 12, padding: 24,
background: '#FFFFFF', borderRadius: 12, padding: 24,
maxWidth: 480, width: '90%',
border: `1px solid ${testResults[model.id].success ? 'rgba(34,197,94,0.3)' : 'rgba(239,68,68,0.3)'}`,
}} onClick={e => e.stopPropagation()}>
@@ -627,9 +627,9 @@ const Models: React.FC = () => {
})}
{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>
<p style={{ color: '#8892B0', marginBottom: '16px' }}>暂无模型供应商,点击上方按钮添加</p>
<p style={{ color: '#5A6480', marginBottom: '16px' }}>暂无模型供应商,点击上方按钮添加</p>
</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 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 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;
+40 -40
View File
@@ -358,7 +358,7 @@ const Storage: React.FC = () => {
</div>
{/* 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
onClick={() => setActiveTab('oss')}
style={{
@@ -422,19 +422,19 @@ const Storage: React.FC = () => {
<div className="filter-bar" style={{ display: 'flex', gap: '12px', marginBottom: '16px', alignItems: 'center' }}>
<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>
{Object.entries(PROVIDER_CONFIG).map(([k, v]) => <option key={k} value={k}>{v.label}</option>)}
</select>
<input type="text" placeholder="Search..." value={keyword}
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 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' }}>
<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 }}>Provider</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 style={{ padding: '12px 16px', textAlign: 'center' }}>
{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 style={{ padding: '12px 16px', textAlign: 'right' }}>
<div style={{ display: 'flex', gap: '6px', justifyContent: 'flex-end' }}>
<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'}
</button>
<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'}
</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>
</div>
</td>
@@ -493,10 +493,10 @@ const Storage: React.FC = () => {
{total > PAGE_SIZE && (
<div style={{ display: 'flex', justifyContent: 'center', gap: '8px', marginTop: '20px' }}>
<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>
<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>
)}
</>
@@ -536,7 +536,7 @@ const Storage: React.FC = () => {
{/* Tier Defaults */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(250px, 1fr))', gap: '16px', marginBottom: '24px' }}>
{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' }}>
<span style={{ fontSize: '14px', fontWeight: 600, color: '#fff' }}>
{(quotaOverview.tier_labels || {})[level] || `Level ${level}`}
@@ -558,7 +558,7 @@ const Storage: React.FC = () => {
{/* Filter Bar */}
<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); }}
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="0">Free</option>
<option value="2">Pro</option>
@@ -566,14 +566,14 @@ const Storage: React.FC = () => {
</select>
<input type="text" placeholder="Search username/email..." value={quotaKeyword}
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>
{/* 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' }}>
<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: '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>
@@ -626,9 +626,9 @@ const Storage: React.FC = () => {
</td>
<td style={{ padding: '12px 16px', textAlign: 'right' }}>
<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={() => 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>
</td>
</tr>
@@ -641,10 +641,10 @@ const Storage: React.FC = () => {
{quotaTotal > PAGE_SIZE && (
<div style={{ display: 'flex', justifyContent: 'center', gap: '8px', marginTop: '20px' }}>
<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>
<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>
)}
</>
@@ -660,9 +660,9 @@ const Storage: React.FC = () => {
justifyContent: 'center', zIndex: 1000,
}}>
<div className="modal-content" style={{
background: '#111627', borderRadius: '12px', padding: '24px',
background: '#FFFFFF', borderRadius: '12px', padding: '24px',
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' }}>
{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>
<input type="text" value={formData.name} onChange={(e) => setFormData({ ...formData, name: e.target.value })}
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>
<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 })}
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="aliyun_oss">Aliyun OSS</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>
<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'}
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 style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px' }}>
<div>
<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 })}
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>
<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 })}
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 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>
<input type="text" value={formData.bucket} onChange={(e) => setFormData({ ...formData, bucket: e.target.value })}
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>
<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 })}
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>
<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 })}
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 style={{ display: 'flex', gap: '20px' }}>
<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>
<textarea value={formData.config_str} onChange={(e) => setFormData({ ...formData, config_str: e.target.value })}
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 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 }}>
{submitting ? 'Saving...' : (editingConfig ? 'Save' : 'Create')}
</button>
@@ -758,9 +758,9 @@ const Storage: React.FC = () => {
justifyContent: 'center', zIndex: 1000,
}}>
<div className="modal-content" style={{
background: '#111627', borderRadius: '12px', padding: '24px',
background: '#FFFFFF', borderRadius: '12px', padding: '24px',
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' }}>
<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' }}>
<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>
@@ -835,9 +835,9 @@ const Storage: React.FC = () => {
justifyContent: 'center', zIndex: 1000,
}}>
<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)',
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>
<div style={{ marginBottom: '16px' }}>
@@ -851,9 +851,9 @@ const Storage: React.FC = () => {
<div style={{ display: 'flex', gap: '8px' }}>
<input type="number" value={adjustValue} onChange={(e) => setAdjustValue(e.target.value)}
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')}
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="MB">MB</option>
</select>
@@ -863,7 +863,7 @@ const Storage: React.FC = () => {
</div>
</div>
<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}
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'}
@@ -91,7 +91,7 @@
width: 120px;
height: 120px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.08);
background: rgba(0, 0, 0, 0.06);
top: -30px;
right: -30px;
}
@@ -102,7 +102,7 @@
width: 80px;
height: 80px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.06);
background: rgba(0, 0, 0, 0.04);
bottom: -20px;
left: -20px;
}