feat: enhance audit logging, add MCP settings, and production docker setup
continuous-integration/drone/push Build is passing
continuous-integration/drone/push Build is passing
- Implemented comprehensive audit logging for Tasks, Users, Settings, Goals, Labels, AI Chat, and Rewards. - Added Admin UI for MCP Server settings and Audit Logs. - Created docker-compose-production.yml with Traefik configuration. - Fixed backend bugs (missing storage methods, route closure). - Added Audit Logging Guidelines.
This commit is contained in:
@@ -17,7 +17,7 @@ import {
|
||||
ContextMenuSeparator,
|
||||
ContextMenuTrigger,
|
||||
} from "@/components/ui/context-menu";
|
||||
import { Clock, Calendar, Play, Pause, MoreHorizontal, Edit, Trash2, Timer, CheckCircle, X, Lock } from "lucide-react";
|
||||
import { Clock, Calendar, Play, Pause, MoreHorizontal, Edit, Trash2, Timer, CheckCircle, X, Lock, CornerDownRight } from "lucide-react";
|
||||
import { Task, Label } from '@shared/schema';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
@@ -150,6 +150,9 @@ export default function TaskCard({ task, onStartTimer, onStopTimer, onEdit, onDe
|
||||
const blockingTasks = task.dependencies?.map(dId => allTasks.find(t => t.id === dId)).filter(t => t && t.status !== 'done') || [];
|
||||
const isBlocked = blockingTasks.length > 0;
|
||||
|
||||
const subtasks = allTasks.filter(t => t.parentTaskId === task.id);
|
||||
const completedSubtasks = subtasks.filter(t => t.status === 'done');
|
||||
|
||||
// Fetch labels to get the label color
|
||||
const { data: labels = [] } = useQuery<Label[]>({
|
||||
queryKey: ['/api/labels'],
|
||||
@@ -231,6 +234,11 @@ export default function TaskCard({ task, onStartTimer, onStopTimer, onEdit, onDe
|
||||
}
|
||||
};
|
||||
|
||||
/* Retrieve parent task if this is a subtask */
|
||||
const parentTask = task.parentTaskId
|
||||
? allTasks.find(t => String(t.id) === String(task.parentTaskId))
|
||||
: null;
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
drag="x"
|
||||
@@ -245,11 +253,13 @@ export default function TaskCard({ task, onStartTimer, onStopTimer, onEdit, onDe
|
||||
<ContextMenuTrigger asChild>
|
||||
<Card
|
||||
className={`p-5 hover-elevate active-elevate-2 transition-all duration-300 relative hover:-translate-y-1 hover:shadow-xl ${isDragging ? 'rotate-1 scale-105 shadow-lg' : ''
|
||||
} ${task.status === 'done' ? 'opacity-60' : ''}`}
|
||||
} ${task.status === 'done' ? 'opacity-60' : ''} ${parentTask ? 'border-l-4 border-l-indigo-400 bg-indigo-50/10' : ''}`}
|
||||
style={taskLabel ? {
|
||||
borderColor: taskLabel.color,
|
||||
borderWidth: '2px',
|
||||
borderStyle: 'solid'
|
||||
borderStyle: 'solid',
|
||||
borderLeftWidth: parentTask ? '4px' : '2px', // Make left border thicker if subtask
|
||||
borderLeftColor: parentTask ? '#818cf8' : taskLabel.color // Indigo for subtask
|
||||
} : {}}
|
||||
data-testid={`card-task-${task.id}`}
|
||||
>
|
||||
@@ -283,8 +293,17 @@ export default function TaskCard({ task, onStartTimer, onStopTimer, onEdit, onDe
|
||||
console.log(`Task card clicked: ${task.title}`);
|
||||
}}
|
||||
>
|
||||
{/* Parent Task Link rendered in title now */}
|
||||
<h3 className={`font-medium text-sm leading-tight truncate ${task.status === 'done' ? 'line-through' : ''}`} data-testid={`text-task-title-${task.id}`}>
|
||||
{task.title}
|
||||
{parentTask ? (
|
||||
<span className="flex items-center gap-1">
|
||||
<span className="text-muted-foreground font-normal">{parentTask.title}</span>
|
||||
<span className="text-muted-foreground">›</span>
|
||||
<span>{task.title}</span>
|
||||
</span>
|
||||
) : (
|
||||
task.title
|
||||
)}
|
||||
</h3>
|
||||
{task.description && (
|
||||
<p className="text-xs text-muted-foreground mt-1 line-clamp-2" data-testid={`text-task-description-${task.id}`}>
|
||||
@@ -312,11 +331,12 @@ export default function TaskCard({ task, onStartTimer, onStopTimer, onEdit, onDe
|
||||
{/* Shared Status Icon */}
|
||||
<SharedTaskIcon task={task} />
|
||||
|
||||
{task.dueDate && (
|
||||
{(task.dueDate || task.startDate) && (
|
||||
<div className="flex items-center gap-1 text-xs text-muted-foreground">
|
||||
<Calendar className="w-3 h-3" />
|
||||
<span data-testid={`text-due-date-${task.id}`}>
|
||||
{task.dueDate.toLocaleDateString()}
|
||||
{task.startDate ? `${new Date(task.startDate).toLocaleDateString()} - ` : ''}
|
||||
{task.dueDate ? new Date(task.dueDate).toLocaleDateString() : ''}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
@@ -328,7 +348,13 @@ export default function TaskCard({ task, onStartTimer, onStopTimer, onEdit, onDe
|
||||
|
||||
{task.estimatedDuration && (
|
||||
<Badge variant="outline" className="text-xs text-muted-foreground border-dashed">
|
||||
⏳ {task.estimatedDuration}m
|
||||
⏳ {formatTime(task.estimatedDuration)}
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
{subtasks.length > 0 && (
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
{completedSubtasks.length}/{subtasks.length} Subtasks
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user