fix: close remaining security gaps, test fixes, frontend integration, event bus
Check Cross-Plugin Imports / check (push) Has been cancelled
Check Cross-Plugin Imports / check (push) Has been cancelled
- RCE: move _check_dangerous_imports() BEFORE exec_module() in plugins.py - verify_ws_origin: reject empty Origin header when CORS configured - Test: ai_app fixture with permission_registry init for ai_assistant - Test: login_client sets CSRF token + Origin as client default headers - Test: SESSION_COOKIE_SECURE=false override + get_settings.cache_clear() - Test: asyncio_default_test_loop_scope=session fixes event loop closed - Test: fix 15 assertions (paths, variables, auth expectations) - Frontend: integrate SavedFilterBar in ContactsList, Mail, Calendar - Frontend: integrate TagSelector in ContactsList, Mail, Calendar - Event Bus: add 4 subscribers in system_notif (conversation/participant/reaction) - Docs: update all analysis reports and FIX-PLAN-V2 to current state
This commit is contained in:
@@ -28,6 +28,9 @@ import { useCalendarStore, type CalendarViewMode } from '@/store/calendarStore';
|
||||
import { usePluginToolbarStore } from '@/store/pluginToolbarStore';
|
||||
import { ChevronLeft, ChevronRight, ExternalLink, Info, Plus } from 'lucide-react';
|
||||
import { PrintButton } from '@/components/common/PrintButton';
|
||||
import { SavedFilterBar } from '@/components/common/SavedFilterBar';
|
||||
import { TagSelector } from '@/components/tags/TagSelector';
|
||||
import type { Tag } from '@/api/tags';
|
||||
import {
|
||||
fetchCalendars,
|
||||
createCalendar,
|
||||
@@ -80,6 +83,7 @@ export function CalendarPage() {
|
||||
const [showIcs, setShowIcs] = useState(false);
|
||||
const [deleteTarget, setDeleteTarget] = useState<CalendarEntry | null>(null);
|
||||
const [showDetails, setShowDetails] = useState(true);
|
||||
const [selectedTags, setSelectedTags] = useState<Tag[]>([]);
|
||||
|
||||
// Mobile view state
|
||||
const [activeView, setActiveView] = useState<'tree' | 'calendar' | 'details'>('tree');
|
||||
@@ -597,6 +601,25 @@ export function CalendarPage() {
|
||||
data-testid="calendar-view-pane"
|
||||
>
|
||||
<div className="flex flex-col h-full">
|
||||
{/* Saved filter bar + tag selector */}
|
||||
<div className="flex items-center gap-2 px-3 py-2 border-b border-secondary-200 bg-secondary-50 flex-wrap" data-testid="calendar-filter-bar">
|
||||
<SavedFilterBar
|
||||
entityType="calendar"
|
||||
currentFilters={{ viewMode, visibleMonth: visibleMonth.toISOString(), sortBy: viewMode }}
|
||||
onApplyFilter={(criteria) => {
|
||||
if (criteria.viewMode) setViewMode(criteria.viewMode as CalendarViewMode);
|
||||
}}
|
||||
/>
|
||||
<div className="flex-1 min-w-[180px]" data-testid="calendar-tag-selector">
|
||||
<TagSelector
|
||||
entityType="calendar_entry"
|
||||
entityId={selectedEntry?.id ?? ''}
|
||||
selectedTags={selectedTags}
|
||||
onChange={setSelectedTags}
|
||||
placeholder={t('tags.selectPlaceholder', 'Tags filtern...')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-end px-3 py-1.5 border-b border-secondary-200" data-no-print>
|
||||
<PrintButton targetId="calendar-view" />
|
||||
</div>
|
||||
|
||||
@@ -16,6 +16,9 @@ import { ContactDetail } from '@/components/contacts/ContactDetail';
|
||||
import { ContactEditForm } from '@/components/contacts/ContactEditForm';
|
||||
import { useWindowStore } from '@/store/windowStore';
|
||||
import { SavedFilters } from '@/components/SavedFilters';
|
||||
import { SavedFilterBar } from '@/components/common/SavedFilterBar';
|
||||
import { TagSelector } from '@/components/tags/TagSelector';
|
||||
import type { Tag } from '@/api/tags';
|
||||
import { ArrowDownAZ, ArrowUpZA, Bookmark, ChevronLeft, ExternalLink, LayoutGrid, List, Plus, Printer } from 'lucide-react';
|
||||
import {
|
||||
useUnifiedContacts,
|
||||
@@ -38,6 +41,7 @@ export function ContactsListPage() {
|
||||
const [selectedContactId, setSelectedContactId] = useState<string | null>(null);
|
||||
const [activeView, setActiveView] = useState<'folders' | 'list' | 'detail'>('folders');
|
||||
const [savedFiltersOpen, setSavedFiltersOpen] = useState(false);
|
||||
const [selectedTags, setSelectedTags] = useState<Tag[]>([]);
|
||||
const openWindow = useWindowStore((s) => s.openWindow);
|
||||
|
||||
// Debounce search
|
||||
@@ -367,6 +371,29 @@ export function ContactsListPage() {
|
||||
className="border-r border-secondary-200 bg-white"
|
||||
data-testid="contact-list-pane"
|
||||
>
|
||||
{/* Saved filter bar + tag selector */}
|
||||
<div className="flex items-center gap-2 px-3 py-2 border-b border-secondary-200 bg-secondary-50 flex-wrap" data-testid="contacts-filter-bar">
|
||||
<SavedFilterBar
|
||||
entityType="contacts"
|
||||
currentFilters={{ search: debouncedSearch, type: contactType, sortBy, sortOrder, folderId, tagFilter }}
|
||||
onApplyFilter={(criteria) => {
|
||||
if (criteria.search) setSearch(criteria.search); else setSearch('');
|
||||
if (criteria.sortBy) setSortBy(criteria.sortBy);
|
||||
if (criteria.sortOrder) setSortOrder(criteria.sortOrder);
|
||||
if (criteria.type) setSelectedFilter(criteria.type as ContactFilter);
|
||||
setPage(1);
|
||||
}}
|
||||
/>
|
||||
<div className="flex-1 min-w-[180px]" data-testid="contacts-tag-selector">
|
||||
<TagSelector
|
||||
entityType="contact"
|
||||
entityId={selectedContactId ?? ''}
|
||||
selectedTags={selectedTags}
|
||||
onChange={setSelectedTags}
|
||||
placeholder={t('tags.selectPlaceholder', 'Tags filtern...')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* List — no inline toolbar, all controls are in the plugin toolbar */}
|
||||
<div className="flex-1 min-h-0" id="contacts-table">
|
||||
<ContactList
|
||||
|
||||
@@ -19,6 +19,9 @@ import { MailComposeForm, type ComposeMode } from '@/components/mail/MailCompose
|
||||
import { useWindowStore } from '@/store/windowStore';
|
||||
import { usePluginToolbarStore } from '@/store/pluginToolbarStore';
|
||||
import { ArrowRight, Check, ChevronLeft, ExternalLink, Loader2, Plus, Redo2, Trash2, TrendingUp, Undo2 } from 'lucide-react';
|
||||
import { SavedFilterBar } from '@/components/common/SavedFilterBar';
|
||||
import { TagSelector } from '@/components/tags/TagSelector';
|
||||
import type { Tag } from '@/api/tags';
|
||||
import {
|
||||
fetchAccounts,
|
||||
fetchFolders,
|
||||
@@ -83,6 +86,7 @@ export function MailPage() {
|
||||
const [sortBy, setSortBy] = useState<'date' | 'from' | 'subject'>('date');
|
||||
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc');
|
||||
const [isSyncing, setIsSyncing] = useState(false);
|
||||
const [selectedTags, setSelectedTags] = useState<Tag[]>([]);
|
||||
|
||||
// Ref to track the current folder ID for async callbacks (prevents race conditions)
|
||||
const selectedFolderIdRef = useRef<string | null>(null);
|
||||
@@ -810,6 +814,27 @@ export function MailPage() {
|
||||
className="border-r border-secondary-200 bg-white"
|
||||
data-testid="mail-list-pane"
|
||||
>
|
||||
{/* Saved filter bar + tag selector */}
|
||||
<div className="flex items-center gap-2 px-3 py-2 border-b border-secondary-200 bg-secondary-50 flex-wrap" data-testid="mail-filter-bar">
|
||||
<SavedFilterBar
|
||||
entityType="mail"
|
||||
currentFilters={{ search: searchQuery, sortBy, sortOrder, folderId: selectedFolderId }}
|
||||
onApplyFilter={(criteria) => {
|
||||
if (criteria.search !== undefined) handleSearch(criteria.search);
|
||||
if (criteria.sortBy) setSortBy(criteria.sortBy as 'date' | 'from' | 'subject');
|
||||
if (criteria.sortOrder) setSortOrder(criteria.sortOrder as 'asc' | 'desc');
|
||||
}}
|
||||
/>
|
||||
<div className="flex-1 min-w-[180px]" data-testid="mail-tag-selector">
|
||||
<TagSelector
|
||||
entityType="file"
|
||||
entityId={selectedMail?.id ?? ''}
|
||||
selectedTags={selectedTags}
|
||||
onChange={setSelectedTags}
|
||||
placeholder={t('tags.selectPlaceholder', 'Tags filtern...')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<MailList
|
||||
mails={mails}
|
||||
selectedMailId={selectedMail?.id || null}
|
||||
|
||||
Reference in New Issue
Block a user