nas-samba v2.0.0: per-user home directories, Samba accounts, remove quotas

- Each user gets a Linux + Samba account and home dir at /nas/home/<username>/
- Web UI file browsing scoped to per-user directories
- Creating/deleting users from web UI also manages system + Samba accounts
- Replaced hardcoded [data] SMB share with [homes] (per-user automatic shares)
- Removed hardcoded alexz user from entrypoint — server.js handles all user management
- Removed storage quotas from backend and frontend
- Existing users get home directories auto-created on startup (migration)
This commit is contained in:
2026-04-26 05:51:23 +00:00
parent 888acdbd7e
commit b1697d6e1e
17 changed files with 216 additions and 221 deletions

View File

@@ -1,13 +1,13 @@
{
"files": {
"main.css": "/static/css/main.f6816838.css",
"main.js": "/static/js/main.2e7681ef.js",
"main.css": "/static/css/main.6f136b27.css",
"main.js": "/static/js/main.f1998df4.js",
"index.html": "/index.html",
"main.f6816838.css.map": "/static/css/main.f6816838.css.map",
"main.2e7681ef.js.map": "/static/js/main.2e7681ef.js.map"
"main.6f136b27.css.map": "/static/css/main.6f136b27.css.map",
"main.f1998df4.js.map": "/static/js/main.f1998df4.js.map"
},
"entrypoints": [
"static/css/main.f6816838.css",
"static/js/main.2e7681ef.js"
"static/css/main.6f136b27.css",
"static/js/main.f1998df4.js"
]
}

View File

@@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#0F172A"/><meta name="description" content="CloudSync - Enterprise File Manager"/><title>CloudSync - Enterprise File Manager</title><script defer="defer" src="/static/js/main.2e7681ef.js"></script><link href="/static/css/main.f6816838.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
<!doctype html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#0F172A"/><meta name="description" content="CloudSync - Enterprise File Manager"/><title>CloudSync - Enterprise File Manager</title><script defer="defer" src="/static/js/main.f1998df4.js"></script><link href="/static/css/main.6f136b27.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -8,7 +8,7 @@ import {
MoreVertical, Download, Trash2, Edit3, Move, Copy,
Share2, ChevronRight, Home, Moon, Sun, LogOut, Users,
Activity, X, Upload, Folder, File, Image,
Film, Music, Archive, Code, FileText, HardDrive,
Film, Music, Archive, Code, FileText,
Link, Shield
} from 'lucide-react';
import { formatBytes, formatDate, cn } from '../lib/utils';
@@ -159,42 +159,9 @@ function Modal({ open, onClose, title, children, size = 'md' }) {
);
}
// Storage Usage Component
function StorageUsage({ used, quota }) {
const percentage = quota > 0 ? (used / quota) * 100 : 0;
const isWarning = percentage > 80;
const isCritical = percentage > 95;
return (
<div className="p-4 bg-card border border-border rounded-lg">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-2">
<HardDrive className="w-4 h-4 text-muted-foreground" />
<span className="text-sm font-medium">Storage</span>
</div>
<span className="text-sm text-muted-foreground">
{formatBytes(used)} / {formatBytes(quota)}
</span>
</div>
<div className="h-2 bg-muted rounded-full overflow-hidden">
<div
className={cn(
"h-full transition-all duration-300",
isCritical ? "bg-destructive" : isWarning ? "bg-amber-500" : "bg-primary"
)}
style={{ width: `${Math.min(percentage, 100)}%` }}
/>
</div>
<p className="text-xs text-muted-foreground mt-2">
{percentage.toFixed(1)}% used
</p>
</div>
);
}
// Main Dashboard Component
function Dashboard() {
const { user, logout, updateUser } = useAuth();
const { user, logout } = useAuth();
const { theme, toggleTheme } = useTheme();
// State
@@ -232,7 +199,7 @@ function Dashboard() {
const [loadingUsers, setLoadingUsers] = useState(false);
const [showUserForm, setShowUserForm] = useState(false);
const [editingUser, setEditingUser] = useState(null);
const [userForm, setUserForm] = useState({ username: '', password: '', name: '', role: 'user', storage_quota: 10737418240 });
const [userForm, setUserForm] = useState({ username: '', password: '', name: '', role: 'user' });
// Activity state
const [activities, setActivities] = useState([]);
@@ -274,11 +241,10 @@ function Dashboard() {
try {
const response = await api.getStats();
setStats(response.data);
updateUser({ storage_used: response.data.storage_used });
} catch (error) {
console.error('Failed to load stats');
}
}, [updateUser]);
}, []);
useEffect(() => {
loadFiles();
@@ -467,7 +433,7 @@ function Dashboard() {
}
try {
if (editingUser) {
const updateData = { name: userForm.name, role: userForm.role, storage_quota: userForm.storage_quota };
const updateData = { name: userForm.name, role: userForm.role };
if (userForm.password) updateData.password = userForm.password;
await api.updateUser(editingUser.id, updateData);
toast.success('User updated');
@@ -477,7 +443,7 @@ function Dashboard() {
}
setShowUserForm(false);
setEditingUser(null);
setUserForm({ username: '', password: '', name: '', role: 'user', storage_quota: 10737418240 });
setUserForm({ username: '', password: '', name: '', role: 'user' });
loadUsers();
} catch (error) {
toast.error(error.response?.data?.detail || 'Failed to save user');
@@ -661,12 +627,7 @@ function Dashboard() {
{/* Main Content */}
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6 space-y-6">
{/* Storage & Stats */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<StorageUsage
used={stats?.storage_used || user?.storage_used || 0}
quota={stats?.storage_quota || user?.storage_quota || 10737418240}
/>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="p-4 bg-card border border-border rounded-lg">
<div className="flex items-center gap-2 mb-2">
<File className="w-4 h-4 text-muted-foreground" />
@@ -1212,24 +1173,9 @@ function Dashboard() {
<option value="admin">Admin</option>
</select>
</div>
<div>
<label className="block text-sm font-medium mb-2">Storage Quota</label>
<select
value={userForm.storage_quota}
onChange={(e) => setUserForm(prev => ({ ...prev, storage_quota: Number(e.target.value) }))}
data-testid="user-quota-select"
className="w-full h-10 px-4 bg-background border border-input rounded-lg focus:outline-none focus:ring-2 focus:ring-ring"
>
<option value={1073741824}>1 GB</option>
<option value={5368709120}>5 GB</option>
<option value={10737418240}>10 GB</option>
<option value={53687091200}>50 GB</option>
<option value={107374182400}>100 GB</option>
</select>
</div>
<div className="flex gap-3 pt-2">
<button
onClick={() => { setShowUserForm(false); setEditingUser(null); setUserForm({ username: '', password: '', name: '', role: 'user', storage_quota: 10737418240 }); }}
onClick={() => { setShowUserForm(false); setEditingUser(null); setUserForm({ username: '', password: '', name: '', role: 'user' }); }}
className="flex-1 h-10 bg-secondary text-secondary-foreground rounded-lg hover:opacity-80"
>
Cancel
@@ -1279,10 +1225,10 @@ function Dashboard() {
</div>
<div className="flex items-center gap-2">
<span className="text-sm text-muted-foreground">
{formatBytes(u.storage_used)} / {formatBytes(u.storage_quota)}
{u.role}
</span>
<button
onClick={() => { setEditingUser(u); setUserForm({ username: u.username, password: '', name: u.name, role: u.role, storage_quota: u.storage_quota }); setShowUserForm(true); }}
onClick={() => { setEditingUser(u); setUserForm({ username: u.username, password: '', name: u.name, role: u.role }); setShowUserForm(true); }}
className="p-2 hover:bg-accent rounded-lg"
data-testid={`edit-user-${u.id}`}
>