3D-Stats Höhe fix, Dateilimits zu Benutzer-Tab, Gesamtlimit statt Anzahl

This commit is contained in:
2026-05-28 12:34:12 +02:00
parent 903b6d204f
commit ab752d7c93
3 changed files with 26 additions and 22 deletions

View File

@@ -183,9 +183,11 @@ router.get('/', authenticate, (req, res) => {
router.post('/', authenticate, (req, res) => { router.post('/', authenticate, (req, res) => {
const uid = req.user.id; const uid = req.user.id;
const maxCount = parseInt(getSetting('file_max_count') || '20'); const maxSizeMb = parseInt(getSetting('file_max_size_mb') || '50');
const count = db.prepare('SELECT COUNT(*) c FROM files WHERE user_id=?').get(uid).c; const totalLimitMb= parseInt(getSetting('file_total_size_mb') || '500');
if (count >= maxCount) return res.status(400).json({ error: `Maximum ${maxCount} Dateien erlaubt` }); const usedBytes = db.prepare('SELECT COALESCE(SUM(size),0) AS s FROM files WHERE user_id=?').get(uid).s;
if (usedBytes >= totalLimitMb * 1024 * 1024)
return res.status(400).json({ error: `Gesamtlimit von ${totalLimitMb} MB erreicht` });
getUpload().single('file')(req, res, err => { getUpload().single('file')(req, res, err => {
if (err) return res.status(400).json({ error: err.message }); if (err) return res.status(400).json({ error: err.message });
if (!req.file) return res.status(400).json({ error: 'Keine Datei' }); if (!req.file) return res.status(400).json({ error: 'Keine Datei' });
@@ -201,8 +203,8 @@ router.get('/storage', authenticate, (req, res) => {
const used = db.prepare('SELECT COALESCE(SUM(size),0) AS s FROM files WHERE user_id=?').get(uid).s; const used = db.prepare('SELECT COALESCE(SUM(size),0) AS s FROM files WHERE user_id=?').get(uid).s;
const count = db.prepare('SELECT COUNT(*) AS c FROM files WHERE user_id=?').get(uid).c; const count = db.prepare('SELECT COUNT(*) AS c FROM files WHERE user_id=?').get(uid).c;
const maxSizeMb = parseInt(getSetting('file_max_size_mb') || '50'); const maxSizeMb = parseInt(getSetting('file_max_size_mb') || '50');
const maxCount = parseInt(getSetting('file_max_count') || '20'); const totalLimitMb = parseInt(getSetting('file_total_size_mb') || '500');
res.json({ used, count, maxSizeMb, maxCount }); res.json({ used, count, maxSizeMb, totalLimitMb });
}); });
router.get('/:id/download', authenticate, async (req, res) => { router.get('/:id/download', authenticate, async (req, res) => {
@@ -295,7 +297,7 @@ router.get('/settings', authenticate, requireAdmin, (req, res) => {
res.json(obj); res.json(obj);
}); });
router.put('/settings', authenticate, requireAdmin, (req, res) => { router.put('/settings', authenticate, requireAdmin, (req, res) => {
for (const key of ['file_max_size_mb','file_max_count','file_allowed_ext']) { for (const key of ['file_max_size_mb','file_total_size_mb','file_allowed_ext']) {
if (req.body[key] !== undefined) if (req.body[key] !== undefined)
db.prepare('INSERT OR REPLACE INTO admin_settings (key,value) VALUES (?,?)').run(key, String(req.body[key])); db.prepare('INSERT OR REPLACE INTO admin_settings (key,value) VALUES (?,?)').run(key, String(req.body[key]));
} }

View File

@@ -686,7 +686,7 @@ function BestellStats() {
<div style={{...S.card,marginBottom:10}}> <div style={{...S.card,marginBottom:10}}>
<button onClick={toggle} style={{width:'100%',background:'transparent',border:'none', <button onClick={toggle} style={{width:'100%',background:'transparent',border:'none',
display:'flex',justifyContent:'space-between',alignItems:'center',cursor:'pointer',padding:0}}> display:'flex',justifyContent:'space-between',alignItems:'center',cursor:'pointer',padding:0}}>
<div style={S.head}>3D-DRUCK STATISTIK</div> <div style={{...S.head, marginBottom:0}}>3D-DRUCK STATISTIK</div>
<span style={{color:'rgba(255,255,255,0.3)',fontSize:11,display:'inline-block', <span style={{color:'rgba(255,255,255,0.3)',fontSize:11,display:'inline-block',
transform:open?'rotate(90deg)':'rotate(0)',transition:'transform 0.2s'}}></span> transform:open?'rotate(90deg)':'rotate(0)',transition:'transform 0.2s'}}></span>
</button> </button>
@@ -1457,7 +1457,12 @@ function AdminPanel({ toast, mobile, user }) {
)} )}
{section === 'benutzer' && user?.role==='admin' && ( {section === 'benutzer' && user?.role==='admin' && (
<div><UserManagement toast={toast}/></div> <div>
<UserManagement toast={toast}/>
<Sec title="DATEI-MANAGER LIMITS">
<DateiSettings toast={toast}/>
</Sec>
</div>
)} )}
{section === 'dashboard' && ( {section === 'dashboard' && (
@@ -1468,9 +1473,6 @@ function AdminPanel({ toast, mobile, user }) {
<Sec title="KALENDER (iCal)"> <Sec title="KALENDER (iCal)">
<CalendarSettings toast={toast}/> <CalendarSettings toast={toast}/>
</Sec> </Sec>
{user?.role==='admin' && <Sec title="DATEI-MANAGER LIMITS">
<DateiSettings toast={toast}/>
</Sec>}
</div> </div>
)} )}

View File

@@ -284,15 +284,15 @@ export default function Dateien({ toast, mobile }) {
border:'1px solid rgba(255,255,255,0.06)',borderRadius:8}}> border:'1px solid rgba(255,255,255,0.06)',borderRadius:8}}>
<div style={{display:'flex',justifyContent:'space-between',alignItems:'center', <div style={{display:'flex',justifyContent:'space-between',alignItems:'center',
color:'rgba(255,255,255,0.45)',fontFamily:'monospace',fontSize:10}}> color:'rgba(255,255,255,0.45)',fontFamily:'monospace',fontSize:10}}>
<span>{fmtSize(storage.used)} belegt · {storage.count}/{storage.maxCount} Dateien</span> <span>{fmtSize(storage.used)} / {storage.totalLimitMb} MB belegt</span>
<span style={{fontSize:9,color:'rgba(255,255,255,0.25)'}}>max. {storage.maxSizeMb} MB pro Datei</span> <span style={{fontSize:9,color:'rgba(255,255,255,0.25)'}}>max. {storage.maxSizeMb} MB pro Datei · {storage.count} Dateien</span>
</div> </div>
<div style={{height:3,borderRadius:3,background:'rgba(255,255,255,0.06)',overflow:'hidden',marginTop:7}}> <div style={{height:3,borderRadius:3,background:'rgba(255,255,255,0.06)',overflow:'hidden',marginTop:7}}>
<div style={{ <div style={{
height:'100%', borderRadius:3, height:'100%', borderRadius:3,
width:`${Math.min(100,(storage.count/storage.maxCount)*100)}%`, width:`${Math.min(100,(storage.used/(storage.totalLimitMb*1024*1024))*100)}%`,
background: storage.count/storage.maxCount > 0.85 ? '#ff6b9d' : background: storage.used/(storage.totalLimitMb*1024*1024) > 0.85 ? '#ff6b9d' :
storage.count/storage.maxCount > 0.6 ? '#ffe66d' : '#4ecdc4', storage.used/(storage.totalLimitMb*1024*1024) > 0.6 ? '#ffe66d' : '#4ecdc4',
transition:'width 0.4s', transition:'width 0.4s',
}}/> }}/>
</div> </div>
@@ -509,9 +509,9 @@ export default function Dateien({ toast, mobile }) {
// ── Datei-Einstellungen (Admin) ─────────────────────────────────────────────── // ── Datei-Einstellungen (Admin) ───────────────────────────────────────────────
export function DateiSettings({ toast }) { export function DateiSettings({ toast }) {
const [s, setS] = useState({file_max_size_mb:'50',file_max_count:'20',file_allowed_ext:'.pdf,.jpg,.jpeg,.png,.gif,.zip,.txt,.docx,.xlsx,.mp4,.stl,.3mf'}); const [s, setS] = useState({file_max_size_mb:'50',file_total_size_mb:'500',file_allowed_ext:'.pdf,.jpg,.jpeg,.png,.gif,.zip,.txt,.docx,.xlsx,.mp4,.stl,.3mf'});
const [busy, setBusy] = useState(false); const [busy, setBusy] = useState(false);
useEffect(()=>{api('/tools/dateien/settings').then(setS).catch(()=>{});},[]); useEffect(()=>{api('/tools/dateien/settings').then(d=>setS(prev=>({...prev,...d}))).catch(()=>{});},[]);
const save = async()=>{ const save = async()=>{
setBusy(true); setBusy(true);
try{await api('/tools/dateien/settings',{method:'PUT',body:s});toast('Gespeichert ✓');} try{await api('/tools/dateien/settings',{method:'PUT',body:s});toast('Gespeichert ✓');}
@@ -520,12 +520,12 @@ export function DateiSettings({ toast }) {
return ( return (
<div> <div>
<div style={{marginBottom:10}}> <div style={{marginBottom:10}}>
<label style={{...S.head,display:'block',marginBottom:4}}>MAX. DATEIGRÖSSE (MB)</label> <label style={{...S.head,display:'block',marginBottom:4}}>MAX. DATEIGRÖSSE PRO UPLOAD (MB)</label>
<input type="number" value={s.file_max_size_mb} onChange={e=>setS(p=>({...p,file_max_size_mb:e.target.value}))} style={{...S.inp,fontSize:15}} min="1"/> <input type="number" value={s.file_max_size_mb} onChange={e=>setS(p=>({...p,file_max_size_mb:e.target.value}))} style={{...S.inp,fontSize:15}} min="1"/>
</div> </div>
<div style={{marginBottom:10}}> <div style={{marginBottom:10}}>
<label style={{...S.head,display:'block',marginBottom:4}}>MAX. DATEIEN PRO BENUTZER</label> <label style={{...S.head,display:'block',marginBottom:4}}>GESAMTLIMIT PRO BENUTZER (MB)</label>
<input type="number" value={s.file_max_count} onChange={e=>setS(p=>({...p,file_max_count:e.target.value}))} style={{...S.inp,fontSize:15}} min="1"/> <input type="number" value={s.file_total_size_mb} onChange={e=>setS(p=>({...p,file_total_size_mb:e.target.value}))} style={{...S.inp,fontSize:15}} min="1"/>
</div> </div>
<div style={{marginBottom:14}}> <div style={{marginBottom:14}}>
<label style={{...S.head,display:'block',marginBottom:4}}>ERLAUBTE ENDUNGEN (kommagetrennt)</label> <label style={{...S.head,display:'block',marginBottom:4}}>ERLAUBTE ENDUNGEN (kommagetrennt)</label>