diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index 45fc86c..fbe7708 100644
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -2919,7 +2919,7 @@ function PublicFileShare({ token }) {
};
useEffect(() => {
- fetch('/api/public/file-share/public/' + token)
+ fetch('/api/public/file-share/' + token)
.then(async r => {
const d = await r.json();
if (!r.ok) { setErrMsg(d.error || 'Link ungültig'); setPhase('error'); return; }
@@ -2931,7 +2931,7 @@ function PublicFileShare({ token }) {
}, [token]);
async function loadFolder(pw2) {
- const res = await fetch('/api/public/file-share/public/' + token + '/folder', {
+ const res = await fetch('/api/public/file-share/' + token + '/folder', {
method:'POST', headers:{'Content-Type':'application/json'},
body: JSON.stringify({ password: pw2 }),
});
@@ -2943,7 +2943,7 @@ function PublicFileShare({ token }) {
async function handlePw() {
if (!pw.trim()) return;
- const res = await fetch('/api/public/file-share/public/' + token + (info?.is_folder ? '/folder' : '/download'), {
+ const res = await fetch('/api/public/file-share/' + token + (info?.is_folder ? '/folder' : '/download'), {
method:'POST', headers:{'Content-Type':'application/json'},
body: JSON.stringify({ password: pw }),
});
@@ -2959,7 +2959,7 @@ function PublicFileShare({ token }) {
setDlLoading(fileId || 'single');
try {
const res = await fetch(
- '/api/public/file-share/public/' + token + (fileId ? '/folder/' + fileId : '/download'),
+ '/api/public/file-share/' + token + (fileId ? '/folder/' + fileId : '/download'),
{ method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ password }) }
);
if (!res.ok) { const d = await res.json(); setErrMsg(d.error); return; }
diff --git a/frontend/src/tools/dateien.jsx b/frontend/src/tools/dateien.jsx
index 80b287f..4b55c96 100644
--- a/frontend/src/tools/dateien.jsx
+++ b/frontend/src/tools/dateien.jsx
@@ -29,8 +29,7 @@ const IconBtn = ({ onClick, color, title, children, stop }) => (
// ── Öffentlicher Link-Share Modal ─────────────────────────────────────────────
function PublicShareModal({ item, itemType, onClose, toast }) {
const [password, setPassword] = useState(() => genPw());
- const [showPw, setShowPw] = useState(false);
- const [expiresH, setExpiresH] = useState(24);
+ const [expiresH, setExpiresH] = useState(1);
const [label, setLabel] = useState('');
const [shares, setShares] = useState([]);
const [creating, setCreating] = useState(false);
@@ -41,8 +40,16 @@ function PublicShareModal({ item, itemType, onClose, toast }) {
const base = '/tools/dateien/public-shares';
function genPw() {
- const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789';
- return Array.from({length:10}, () => chars[Math.floor(Math.random()*chars.length)]).join('');
+ const upper = 'ABCDEFGHJKLMNPQRSTUVWXYZ';
+ const lower = 'abcdefghjkmnpqrstuvwxyz';
+ const digits = '23456789';
+ const special = '!@#$%&*-+?';
+ const all = upper + lower + digits + special;
+ // Mindestens je 1 aus jeder Kategorie, Rest zufällig
+ const pick = s => s[Math.floor(Math.random()*s.length)];
+ const base = [pick(upper), pick(lower), pick(digits), pick(special)];
+ const rest = Array.from({length:8}, () => pick(all));
+ return [...base, ...rest].sort(() => Math.random()-0.5).join('');
}
useEffect(() => {
@@ -120,18 +127,14 @@ function PublicShareModal({ item, itemType, onClose, toast }) {
{/* Passwort Pflicht + Generator */}
-
+
setPassword(e.target.value)}
placeholder="Passwort (Pflicht)"
- style={{...S.inp, width:'100%', boxSizing:'border-box', paddingRight:36}}
+ style={{...S.inp, width:'100%', boxSizing:'border-box', fontFamily:'monospace', letterSpacing:1}}
/>
-
)}
{shareItem &&
{setShareItem(null);load();}} toast={toast}/>}
- {publicShareItem && setPublicShareItem(null)} toast={toast}/>}
+ {publicShareItem && {setPublicShareItem(null);load();}} toast={toast}/>}
+
+ {/* ── Aktive öffentliche Links ──────────────────────────────────────── */}
+ {publicShares.length > 0 && tab === 'own' && (
+
+
setShowPublicLinks(v=>!v)}>
+
+ 🔗
+
+ Aktive öffentliche Links
+
+
+ {publicShares.length}
+
+
+
{showPublicLinks ? '▲' : '▼'}
+
+ {showPublicLinks && publicShares.map(s => {
+ const name = s.file_name || s.folder_name || s.label || '—';
+ const isExp = s.expires_at && s.expires_at < Math.floor(Date.now()/1000);
+ const fmtExp = exp => {
+ if (!exp) return '∞';
+ const d = new Date(exp*1000);
+ return d.toLocaleDateString('de-DE')+' '+d.toLocaleTimeString('de-DE',{hour:'2-digit',minute:'2-digit'});
+ };
+ return (
+
+
{s.file_id ? '📄' : '📁'}
+
+
+ {name}
+ {s.label && s.label !== name && ({s.label})}
+
+
+ {isExp?'⚠ Abgelaufen':'Ablauf: '}{!isExp&&fmtExp(s.expires_at)} · {s.download_count} Abrufe · 🔒
+
+
+
+
+
+ );
+ })}
+
+ )}
{pwPrompt && { setPwPrompt(null); download(pwPrompt, pw); }}