Merge pull request #8 from cadtoolbox/copilot/hide-uploaded-by-column

This commit is contained in:
Thomas Rambach
2026-02-04 05:27:13 -05:00
committed by GitHub
6 changed files with 240 additions and 123 deletions
@@ -614,4 +614,115 @@ describe('FileManagerPage', () => {
});
});
});
describe('authentication-based UI changes', () => {
it('hides "Uploaded By" column and user filter when auth is disabled', async () => {
// Mock auth disabled (default)
server.use(
http.get('*/api/v1/auth/status', () => {
return HttpResponse.json({
auth_enabled: false,
requires_setup: false,
});
}),
http.get('/api/v1/library/files', () => {
return HttpResponse.json([
{
id: 1,
filename: 'test.3mf',
file_path: '/library/test.3mf',
file_size: 1048576,
file_type: '3mf',
folder_id: null,
thumbnail_path: null,
print_name: 'Test File',
print_time_seconds: 3600,
print_count: 0,
duplicate_count: 0,
created_at: '2024-01-01T00:00:00Z',
created_by_username: 'testuser',
},
]);
})
);
render(<FileManagerPage />);
// Switch to list view to see the column headers
await waitFor(() => {
expect(screen.getByText('Test File')).toBeInTheDocument();
});
const user = userEvent.setup();
const listViewButton = screen.getByRole('button', { name: /list/i });
await user.click(listViewButton);
// "Uploaded By" column header should not be present
await waitFor(() => {
expect(screen.queryByText('Uploaded By')).not.toBeInTheDocument();
});
// User filter dropdown should not be present
expect(screen.queryByPlaceholderText('Filter by user')).not.toBeInTheDocument();
});
it('shows "Uploaded By" column and user filter when auth is enabled', async () => {
// Mock auth enabled
server.use(
http.get('*/api/v1/auth/status', () => {
return HttpResponse.json({
auth_enabled: true,
requires_setup: false,
});
}),
http.get('/api/v1/library/files', () => {
return HttpResponse.json([
{
id: 1,
filename: 'test.3mf',
file_path: '/library/test.3mf',
file_size: 1048576,
file_type: '3mf',
folder_id: null,
thumbnail_path: null,
print_name: 'Test File',
print_time_seconds: 3600,
print_count: 0,
duplicate_count: 0,
created_at: '2024-01-01T00:00:00Z',
created_by_username: 'testuser',
},
]);
}),
http.get('/api/v1/users/', () => {
return HttpResponse.json([
{ id: 1, username: 'testuser' },
{ id: 2, username: 'admin' },
]);
})
);
render(<FileManagerPage />);
// Switch to list view to see the column headers
await waitFor(() => {
expect(screen.getByText('Test File')).toBeInTheDocument();
});
const user = userEvent.setup();
const listViewButton = screen.getByRole('button', { name: /list/i });
await user.click(listViewButton);
// "Uploaded By" column header should be present
await waitFor(() => {
expect(screen.getByText('Uploaded By')).toBeInTheDocument();
});
// User filter dropdown should be present
expect(screen.getByPlaceholderText('Filter by user')).toBeInTheDocument();
// Username should be displayed in the column
expect(screen.getByText('testuser')).toBeInTheDocument();
});
});
});
+48 -42
View File
@@ -912,10 +912,11 @@ interface FileCardProps {
thumbnailVersion?: number;
hasPermission: (permission: Permission) => boolean;
canModify: (resource: 'queue' | 'archives' | 'library', action: 'update' | 'delete' | 'reprint', createdById: number | null | undefined) => boolean;
authEnabled: boolean;
t: TFunction;
}
function FileCard({ file, isSelected, isMobile, onSelect, onDelete, onDownload, onAddToQueue, onPrint, onRename, onGenerateThumbnail, thumbnailVersion, hasPermission, canModify, t }: FileCardProps) {
function FileCard({ file, isSelected, isMobile, onSelect, onDelete, onDownload, onAddToQueue, onPrint, onRename, onGenerateThumbnail, thumbnailVersion, hasPermission, canModify, authEnabled, t }: FileCardProps) {
const [showActions, setShowActions] = useState(false);
return (
@@ -974,7 +975,7 @@ function FileCard({ file, isSelected, isMobile, onSelect, onDelete, onDownload,
{t('fileManager.printedCount', { count: file.print_count })}
</div>
)}
{file.created_by_username && (
{authEnabled && file.created_by_username && (
<div className="mt-1 text-xs text-bambu-gray flex items-center gap-1">
<User className="w-3 h-3" />
{file.created_by_username}
@@ -1089,7 +1090,7 @@ export function FileManagerPage() {
const { t } = useTranslation();
const queryClient = useQueryClient();
const { showToast } = useToast();
const { hasPermission, hasAnyPermission, canModify } = useAuth();
const { hasPermission, hasAnyPermission, canModify, authEnabled } = useAuth();
const [searchParams] = useSearchParams();
// Read folder ID from URL query parameter
@@ -1762,31 +1763,33 @@ export function FileManagerPage() {
</select>
</div>
{/* Username filter with autocomplete */}
<div className="relative">
<input
type="text"
placeholder={t('fileManager.filterByUser', { defaultValue: 'Filter by user' })}
value={filterUsername}
onChange={(e) => setFilterUsername(e.target.value)}
list="usernames-list"
className={`w-32 sm:w-40 px-2 py-1.5 bg-bambu-dark border border-bambu-dark-tertiary rounded text-sm text-white placeholder-bambu-gray focus:outline-none focus:border-bambu-green ${filterUsername ? 'pr-7' : ''}`}
style={filterUsername ? { WebkitAppearance: 'none', MozAppearance: 'textfield' } : undefined}
/>
{filterUsername && (
<button
onClick={() => setFilterUsername('')}
className="absolute right-2 top-1/2 -translate-y-1/2 text-bambu-gray hover:text-white z-10"
>
<X className="w-3 h-3" />
</button>
)}
<datalist id="usernames-list">
{users?.map((user) => (
<option key={user.id} value={user.username} />
))}
</datalist>
</div>
{/* Username filter with autocomplete - only show when auth is enabled */}
{authEnabled && (
<div className="relative">
<input
type="text"
placeholder={t('fileManager.filterByUser', { defaultValue: 'Filter by user' })}
value={filterUsername}
onChange={(e) => setFilterUsername(e.target.value)}
list="usernames-list"
className={`w-32 sm:w-40 px-2 py-1.5 bg-bambu-dark border border-bambu-dark-tertiary rounded text-sm text-white placeholder-bambu-gray focus:outline-none focus:border-bambu-green ${filterUsername ? 'pr-7' : ''}`}
style={filterUsername ? { WebkitAppearance: 'none', MozAppearance: 'textfield' } : undefined}
/>
{filterUsername && (
<button
onClick={() => setFilterUsername('')}
className="absolute right-2 top-1/2 -translate-y-1/2 text-bambu-gray hover:text-white z-10"
>
<X className="w-3 h-3" />
</button>
)}
<datalist id="usernames-list">
{users?.map((user) => (
<option key={user.id} value={user.username} />
))}
</datalist>
</div>
)}
{/* Sort */}
<div className="flex items-center gap-2">
@@ -1995,6 +1998,7 @@ export function FileManagerPage() {
thumbnailVersion={thumbnailVersions[file.id]}
hasPermission={hasPermission}
canModify={canModify}
authEnabled={authEnabled}
/>
))}
</div>
@@ -2003,10 +2007,10 @@ export function FileManagerPage() {
<div className="flex-1 lg:overflow-y-auto">
<div className="bg-bambu-dark-secondary rounded-lg border border-bambu-dark-tertiary overflow-hidden">
{/* List header - hidden on mobile, show simplified on small screens */}
<div className="hidden sm:grid grid-cols-[auto_1fr_120px_100px_100px_100px_80px] gap-4 px-4 py-2 bg-bambu-dark-secondary border-b border-bambu-dark-tertiary text-xs text-bambu-gray font-medium">
<div className={`hidden sm:grid ${authEnabled ? 'grid-cols-[auto_1fr_120px_100px_100px_100px_80px]' : 'grid-cols-[auto_1fr_100px_100px_100px_80px]'} gap-4 px-4 py-2 bg-bambu-dark-secondary border-b border-bambu-dark-tertiary text-xs text-bambu-gray font-medium`}>
<div className="w-6" />
<div>{t('common.name')}</div>
<div>{t('fileManager.uploadedBy', { defaultValue: 'Uploaded By' })}</div>
{authEnabled && <div>{t('fileManager.uploadedBy', { defaultValue: 'Uploaded By' })}</div>}
<div>{t('common.type')}</div>
<div>{t('fileManager.size')}</div>
<div>{t('fileManager.prints')}</div>
@@ -2016,7 +2020,7 @@ export function FileManagerPage() {
{filteredAndSortedFiles.map((file) => (
<div
key={file.id}
className={`grid grid-cols-[auto_1fr_120px_100px_100px_100px_80px] gap-4 px-4 py-3 items-center border-b border-bambu-dark-tertiary last:border-b-0 cursor-pointer hover:bg-bambu-dark/50 transition-colors ${
className={`grid ${authEnabled ? 'grid-cols-[auto_1fr_120px_100px_100px_100px_80px]' : 'grid-cols-[auto_1fr_100px_100px_100px_80px]'} gap-4 px-4 py-3 items-center border-b border-bambu-dark-tertiary last:border-b-0 cursor-pointer hover:bg-bambu-dark/50 transition-colors ${
selectedFiles.includes(file.id) ? 'bg-bambu-green/10' : ''
}`}
onClick={() => handleFileSelect(file.id)}
@@ -2062,17 +2066,19 @@ export function FileManagerPage() {
<div className="text-sm text-white truncate">{file.print_name || file.filename}</div>
</div>
</div>
{/* Uploaded By */}
<div className="text-sm text-bambu-gray flex items-center gap-1">
{file.created_by_username ? (
<>
<User className="w-3 h-3" />
<span className="truncate">{file.created_by_username}</span>
</>
) : (
'-'
)}
</div>
{/* Uploaded By - only show when auth is enabled */}
{authEnabled && (
<div className="text-sm text-bambu-gray flex items-center gap-1">
{file.created_by_username ? (
<>
<User className="w-3 h-3" />
<span className="truncate">{file.created_by_username}</span>
</>
) : (
'-'
)}
</div>
)}
{/* Type */}
<div>
<span className={`text-xs px-1.5 py-0.5 rounded font-medium ${
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
+2 -2
View File
@@ -23,8 +23,8 @@
<!-- Splash screens for iOS -->
<link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
<script type="module" crossorigin src="/assets/index-BaTXGNB2.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BmrIrAHa.css">
<script type="module" crossorigin src="/assets/index-kl_IYtg7.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-B7d6gcUW.css">
</head>
<body>
<div id="root"></div>