feat: Labels-Feature vollständig entfernen (PROJ-9)
Backend: - internal/labelstore/ gelöscht (Store, Schema, CRUD) - internal/api/label_handlers.go gelöscht (alle Label-Routen) - internal/api/server.go: labels-Feld + SetLabels() entfernt - internal/api/search_handlers.go: label_id-Filter + Enrichment entfernt - internal/index/index.go: LabelID aus SearchRequest entfernt - internal/imapserver/server.go: labels-Feld + labelbasierte Mailboxen entfernt - cmd/archivmail/main.go: labelstore-Init + SetLabels() entfernt - cmd/archivmail/version.go: labelstore-Modul entfernt, index-Kommentar korrigiert Frontend: - LabelList.tsx, LabelPicker.tsx, LabelsTab.tsx gelöscht - src/lib/api/system.ts: MailLabel/LabelRule-Typen + alle Label-Funktionen entfernt - src/lib/api/index.ts: Label-Exports entfernt - src/app/search/page.tsx: LabelList + selectedLabelId State entfernt - src/app/mail/[id]/page.tsx: LabelPicker + Labels-State entfernt - src/app/admin/page.tsx: LabelsTab + alle Label-Handler/State entfernt Docs: - features/PROJ-9: Status auf Removed gesetzt - features/INDEX.md: PROJ-9 auf Removed gesetzt Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
4d1bdb6e8b
commit
fdb25cb16a
@@ -28,12 +28,6 @@ import {
|
||||
getTenantDomains,
|
||||
addTenantDomain,
|
||||
removeTenantDomain,
|
||||
getAdminLabels,
|
||||
createAdminLabel,
|
||||
deleteAdminLabel,
|
||||
getLabelRules,
|
||||
createLabelRule,
|
||||
deleteLabelRule,
|
||||
getTenantLogoUrl,
|
||||
uploadTenantLogo,
|
||||
deleteTenantLogo,
|
||||
@@ -50,8 +44,6 @@ import {
|
||||
type Tenant,
|
||||
type TenantDefaultUser,
|
||||
type TenantDomain,
|
||||
type MailLabel,
|
||||
type LabelRule,
|
||||
getCertInfo,
|
||||
uploadCert,
|
||||
generateSelfSignedCert,
|
||||
@@ -72,7 +64,6 @@ import { SecurityTab } from "@/components/admin/tabs/SecurityTab";
|
||||
import { LDAPTab } from "@/components/admin/tabs/LDAPTab";
|
||||
import { TenantLDAPTab } from "@/components/admin/tabs/TenantLDAPTab";
|
||||
import { TenantsTab } from "@/components/admin/tabs/TenantsTab";
|
||||
import { LabelsTab } from "@/components/admin/tabs/LabelsTab";
|
||||
import { CertTab } from "@/components/admin/tabs/CertTab";
|
||||
import { ModulesTab } from "@/components/admin/ModulesTab";
|
||||
import { IMAPSettingsTab } from "@/components/admin/tabs/IMAPSettingsTab";
|
||||
@@ -235,19 +226,6 @@ export default function AdminPage() {
|
||||
handleSyncLDAPUsers,
|
||||
} = useTenantUsers();
|
||||
|
||||
// Labels state
|
||||
const [adminLabels, setAdminLabels] = useState<MailLabel[]>([]);
|
||||
const [adminLabelsLoading, setAdminLabelsLoading] = useState(false);
|
||||
const [adminLabelsError, setAdminLabelsError] = useState("");
|
||||
const [newLabelName, setNewLabelName] = useState("");
|
||||
const [newLabelColor, setNewLabelColor] = useState("#ef4444");
|
||||
const [labelCreating, setLabelCreating] = useState(false);
|
||||
const [labelRules, setLabelRules] = useState<LabelRule[]>([]);
|
||||
const [labelRulesLoading, setLabelRulesLoading] = useState(false);
|
||||
const [newRuleField, setNewRuleField] = useState("from_domain");
|
||||
const [newRuleValue, setNewRuleValue] = useState("");
|
||||
const [newRuleLabelId, setNewRuleLabelId] = useState<number | null>(null);
|
||||
const [ruleCreating, setRuleCreating] = useState(false);
|
||||
|
||||
// Certificate state
|
||||
const [certInfo, setCertInfo] = useState<CertInfo | null>(null);
|
||||
@@ -535,87 +513,6 @@ export default function AdminPage() {
|
||||
}
|
||||
}, []);
|
||||
|
||||
const loadAdminLabels = useCallback(async () => {
|
||||
setAdminLabelsLoading(true);
|
||||
setAdminLabelsError("");
|
||||
try {
|
||||
const data = await getAdminLabels();
|
||||
setAdminLabels(data || []);
|
||||
} catch {
|
||||
setAdminLabelsError("Labels konnten nicht geladen werden.");
|
||||
} finally {
|
||||
setAdminLabelsLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const loadLabelRules = useCallback(async () => {
|
||||
setLabelRulesLoading(true);
|
||||
try {
|
||||
const data = await getLabelRules();
|
||||
setLabelRules(data || []);
|
||||
} catch {
|
||||
// ignore
|
||||
} finally {
|
||||
setLabelRulesLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
function loadLabelsTab() {
|
||||
loadAdminLabels();
|
||||
loadLabelRules();
|
||||
}
|
||||
|
||||
async function handleCreateAdminLabel(e: React.FormEvent) {
|
||||
e.preventDefault();
|
||||
if (!newLabelName.trim()) return;
|
||||
setLabelCreating(true);
|
||||
try {
|
||||
await createAdminLabel(newLabelName.trim(), newLabelColor);
|
||||
setNewLabelName("");
|
||||
setNewLabelColor("#ef4444");
|
||||
await loadAdminLabels();
|
||||
} catch (err) {
|
||||
setAdminLabelsError(err instanceof Error ? err.message : "Fehler");
|
||||
} finally {
|
||||
setLabelCreating(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDeleteAdminLabel(id: number, name: string) {
|
||||
if (!window.confirm(`Globales Label "${name}" wirklich loeschen?`)) return;
|
||||
try {
|
||||
await deleteAdminLabel(id);
|
||||
await loadAdminLabels();
|
||||
await loadLabelRules();
|
||||
} catch (err) {
|
||||
setAdminLabelsError(err instanceof Error ? err.message : "Fehler");
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCreateRule(e: React.FormEvent) {
|
||||
e.preventDefault();
|
||||
if (!newRuleValue.trim() || !newRuleLabelId) return;
|
||||
setRuleCreating(true);
|
||||
try {
|
||||
await createLabelRule(newRuleField, newRuleValue.trim(), newRuleLabelId);
|
||||
setNewRuleValue("");
|
||||
await loadLabelRules();
|
||||
} catch (err) {
|
||||
setAdminLabelsError(err instanceof Error ? err.message : "Fehler");
|
||||
} finally {
|
||||
setRuleCreating(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDeleteRule(id: number) {
|
||||
if (!window.confirm("Regel wirklich loeschen?")) return;
|
||||
try {
|
||||
await deleteLabelRule(id);
|
||||
await loadLabelRules();
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCreateTenant(e: React.FormEvent) {
|
||||
e.preventDefault();
|
||||
@@ -806,7 +703,6 @@ export default function AdminPage() {
|
||||
{!isSuperAdmin && user?.role === "domain_admin" && (
|
||||
<TabsTrigger value="imap-settings">IMAP</TabsTrigger>
|
||||
)}
|
||||
{isSuperAdmin && <TabsTrigger value="labels" onClick={loadLabelsTab}>Labels</TabsTrigger>}
|
||||
{isSuperAdmin && <TabsTrigger value="security">Security</TabsTrigger>}
|
||||
{isSuperAdmin && <TabsTrigger value="cert" onClick={loadCert}>Zertifikat</TabsTrigger>}
|
||||
{isSuperAdmin && <TabsTrigger value="tenants" onClick={loadTenants}>Mandanten</TabsTrigger>}
|
||||
@@ -957,34 +853,6 @@ export default function AdminPage() {
|
||||
</TabsContent>
|
||||
)}
|
||||
|
||||
{isSuperAdmin && (
|
||||
<TabsContent value="labels">
|
||||
<LabelsTab
|
||||
adminLabels={adminLabels}
|
||||
adminLabelsLoading={adminLabelsLoading}
|
||||
adminLabelsError={adminLabelsError}
|
||||
newLabelName={newLabelName}
|
||||
setNewLabelName={setNewLabelName}
|
||||
newLabelColor={newLabelColor}
|
||||
setNewLabelColor={setNewLabelColor}
|
||||
labelCreating={labelCreating}
|
||||
labelRules={labelRules}
|
||||
labelRulesLoading={labelRulesLoading}
|
||||
newRuleField={newRuleField}
|
||||
setNewRuleField={setNewRuleField}
|
||||
newRuleValue={newRuleValue}
|
||||
setNewRuleValue={setNewRuleValue}
|
||||
newRuleLabelId={newRuleLabelId}
|
||||
setNewRuleLabelId={setNewRuleLabelId}
|
||||
ruleCreating={ruleCreating}
|
||||
onCreateLabel={handleCreateAdminLabel}
|
||||
onDeleteLabel={handleDeleteAdminLabel}
|
||||
onCreateRule={handleCreateRule}
|
||||
onDeleteRule={handleDeleteRule}
|
||||
/>
|
||||
</TabsContent>
|
||||
)}
|
||||
|
||||
{isSuperAdmin && (
|
||||
<TabsContent value="cert">
|
||||
<CertTab
|
||||
|
||||
@@ -7,15 +7,11 @@ import {
|
||||
downloadMailAttachment,
|
||||
downloadMailRaw,
|
||||
exportMailPDF,
|
||||
getLabels,
|
||||
getMailLabelIds,
|
||||
type MailDetail,
|
||||
type MailAttachment,
|
||||
type MailLabel,
|
||||
} from "@/lib/api";
|
||||
import { useAuth } from "@/hooks/useAuth";
|
||||
import { Navbar } from "@/components/navbar";
|
||||
import { LabelPicker } from "@/components/LabelPicker";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
||||
@@ -260,10 +256,6 @@ export default function MailViewPage({
|
||||
const [downloading, setDownloading] = useState(false);
|
||||
const [pdfLoading, setPdfLoading] = useState(false);
|
||||
|
||||
// Labels state
|
||||
const [allLabels, setAllLabels] = useState<MailLabel[]>([]);
|
||||
const [assignedLabelIds, setAssignedLabelIds] = useState<number[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!user) return;
|
||||
getMail(id)
|
||||
@@ -272,16 +264,9 @@ export default function MailViewPage({
|
||||
setError(e instanceof Error ? e.message : "Unbekannter Fehler")
|
||||
)
|
||||
.finally(() => setLoading(false));
|
||||
// Load labels
|
||||
getLabels().then(setAllLabels).catch(() => {});
|
||||
loadMailLabels();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [id, user]);
|
||||
|
||||
function loadMailLabels() {
|
||||
getMailLabelIds(id).then(setAssignedLabelIds).catch(() => setAssignedLabelIds([]));
|
||||
}
|
||||
|
||||
async function handleEmlDownload() {
|
||||
setDownloading(true);
|
||||
try {
|
||||
@@ -380,20 +365,6 @@ export default function MailViewPage({
|
||||
</CardHeader>
|
||||
</Card>
|
||||
|
||||
{/* Labels */}
|
||||
{allLabels.length > 0 && (
|
||||
<Card>
|
||||
<CardContent className="pt-4 pb-4">
|
||||
<LabelPicker
|
||||
emailId={id}
|
||||
assignedLabelIds={assignedLabelIds}
|
||||
allLabels={allLabels}
|
||||
onUpdate={loadMailLabels}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Body */}
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
|
||||
+1
-21
@@ -5,7 +5,6 @@ import { useRouter } from "next/navigation";
|
||||
import { useAuth } from "@/hooks/useAuth";
|
||||
import { searchEmails, exportMailsZIP, uploadMailFilesUser, getUploadProgressUser, type SearchHit, type UploadJob } from "@/lib/api";
|
||||
import { Navbar } from "@/components/navbar";
|
||||
import { LabelList } from "@/components/LabelList";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
@@ -57,7 +56,6 @@ export default function SearchPage() {
|
||||
const [dateTo, setDateTo] = useState("");
|
||||
const [sort, setSort] = useState("date_desc");
|
||||
const [hasAttachment, setHasAttachment] = useState<boolean | undefined>(undefined);
|
||||
const [selectedLabelId, setSelectedLabelId] = useState<number | null>(null);
|
||||
|
||||
const [results, setResults] = useState<SearchHit[]>([]);
|
||||
const [total, setTotal] = useState(0);
|
||||
@@ -96,7 +94,6 @@ export default function SearchPage() {
|
||||
date_to: dateTo || undefined,
|
||||
sort: sort !== "date_desc" ? sort : undefined,
|
||||
has_attachment: hasAttachment,
|
||||
label_id: selectedLabelId ?? undefined,
|
||||
page: p,
|
||||
page_size: PAGE_SIZE,
|
||||
});
|
||||
@@ -111,7 +108,7 @@ export default function SearchPage() {
|
||||
setSearching(false);
|
||||
}
|
||||
},
|
||||
[query, fromFilter, toFilter, dateFrom, dateTo, sort, hasAttachment, selectedLabelId]
|
||||
[query, fromFilter, toFilter, dateFrom, dateTo, sort, hasAttachment]
|
||||
);
|
||||
|
||||
// Superadmin has no mail access — redirect to admin dashboard
|
||||
@@ -139,13 +136,6 @@ export default function SearchPage() {
|
||||
.finally(() => setSearching(false));
|
||||
}, [user]);
|
||||
|
||||
// Re-search when label selection changes
|
||||
useEffect(() => {
|
||||
if (!user || !searched) return;
|
||||
doSearch(1);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [selectedLabelId]);
|
||||
|
||||
function handleSubmit(e: React.FormEvent) {
|
||||
e.preventDefault();
|
||||
doSearch(1);
|
||||
@@ -234,16 +224,6 @@ export default function SearchPage() {
|
||||
)}
|
||||
{!authLoading && user && (<>
|
||||
<div className="flex gap-6">
|
||||
{/* Label sidebar — nur für Rollen mit eigenen Mails */}
|
||||
{user.role !== "auditor" && user.role !== "domain_auditor" && (
|
||||
<div className="hidden md:block w-48 shrink-0">
|
||||
<LabelList
|
||||
selectedLabelId={selectedLabelId}
|
||||
onLabelSelect={setSelectedLabelId}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Main content */}
|
||||
<div className="flex-1 min-w-0">
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
|
||||
Reference in New Issue
Block a user