import { useState, useEffect, useCallback } from 'react'; import { Loader2 } from 'lucide-react'; import { Modal, ModalHeader, ModalBody, ModalFooter } from '@/components/ui/modal'; import { Button } from '@/components/ui/button'; import { toast } from '@/components/ui/toast-store'; import { getStackEntryPermissions, setStackEntryPermissions } from '@/lib/stackFilesApi'; import { cn } from '@/lib/utils'; // --------------------------------------------------------------------------- // Bit mapping: owner (6-8), group (3-5), other (0-2) // Within each set: read=4, write=2, execute=1 // --------------------------------------------------------------------------- interface BitInfo { label: 'r' | 'w' | 'x'; shift: number; // bit position } const BITS: BitInfo[] = [ { label: 'r', shift: 2 }, { label: 'w', shift: 1 }, { label: 'x', shift: 0 }, ]; interface Category { label: string; baseShift: number; // owner=6, group=3, other=0 } const CATEGORIES: Category[] = [ { label: 'Owner', baseShift: 6 }, { label: 'Group', baseShift: 3 }, { label: 'Other', baseShift: 0 }, ]; function getBit(mode: number, totalShift: number): boolean { return Boolean(mode & (1 << totalShift)); } function toggleBit(mode: number, totalShift: number): number { return mode ^ (1 << totalShift); } // --------------------------------------------------------------------------- interface FilePermissionsDialogProps { open: boolean; onOpenChange: (open: boolean) => void; stackName: string; relPath: string; entryName: string; rootId?: string; canEdit: boolean; } export function FilePermissionsDialog({ open, onOpenChange, stackName, relPath, entryName, rootId, canEdit, }: FilePermissionsDialogProps) { const [mode, setMode] = useState(0o644); const [loading, setLoading] = useState(false); const [saving, setSaving] = useState(false); const [error, setError] = useState(null); const load = useCallback(async () => { setLoading(true); setError(null); try { const result = await getStackEntryPermissions(stackName, relPath, rootId); setMode(result.mode); } catch (e) { setError(e instanceof Error ? e.message : 'Failed to load permissions.'); } finally { setLoading(false); } }, [stackName, relPath, rootId]); useEffect(() => { if (open) void load(); }, [open, load]); const handleClose = (next: boolean) => { if (saving) return; onOpenChange(next); }; const handleSave = async () => { setSaving(true); try { await setStackEntryPermissions(stackName, relPath, mode, rootId); toast.success('Permissions updated.'); onOpenChange(false); } catch (e) { toast.error(e instanceof Error ? e.message : 'Failed to update permissions.'); } finally { setSaving(false); } }; const octal = mode.toString(8).padStart(3, '0'); const canModify = canEdit; return ( {loading ? (
) : error ? (

{error}

) : (
{/* bit grid */}
{/* header row */}
{/* empty corner */} {BITS.map((b) => (
{b.label}
))} {/* category rows */} {CATEGORIES.map((cat) => ( <>
{cat.label}
{BITS.map((bit) => { const totalShift = cat.baseShift + bit.shift; const checked = getBit(mode, totalShift); return ( ); })} ))}
{/* octal summary */}
Octal {octal}
)} handleClose(false)} disabled={saving}> {canModify ? 'Cancel' : 'Close'} } primary={ canModify ? ( ) : null } /> ); }