feat: implement candidate security honeypots and redesign authenticated layout
This commit is contained in:
@@ -356,6 +356,22 @@ const barColor = (pct) => pct >= 80 ? 'bg-success' : pct >= 60 ? 'bg-highlight'
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Security Alert Badge -->
|
||||
<div v-if="candidate.user.security_alerts?.length" class="bg-accent/10 border border-accent/20 rounded-2xl p-5 relative overflow-hidden">
|
||||
<div class="absolute top-0 right-0 w-24 h-24 bg-[radial-gradient(circle_at_top_right,_var(--tw-gradient-stops))] from-accent/20 to-transparent"></div>
|
||||
<div class="flex items-center gap-3 mb-2 relative z-10">
|
||||
<div class="w-8 h-8 rounded-full bg-accent/20 flex items-center justify-center shrink-0">
|
||||
<svg class="w-4 h-4 text-accent" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
|
||||
</div>
|
||||
<p class="text-[11px] font-black uppercase tracking-[0.1em] text-accent leading-tight">
|
||||
{{ candidate.user.security_alerts.length }} Alerte{{ candidate.user.security_alerts.length > 1 ? 's' : '' }} de sécurité
|
||||
</p>
|
||||
</div>
|
||||
<button @click="activeTab = 'security'" class="relative z-10 mt-1 text-[10px] font-bold uppercase tracking-widest text-accent/70 hover:text-accent transition-colors flex items-center gap-1">
|
||||
Voir les détails <svg class="w-3 h-3" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- AI Summary card (if analysed) -->
|
||||
<div v-if="aiAnalysis" class="bg-surface rounded-2xl border border-ink/[0.07] shadow-sm p-5">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
@@ -410,7 +426,8 @@ const barColor = (pct) => pct >= 80 ? 'bg-success' : pct >= 60 ? 'bg-highlight'
|
||||
{ id:'interview', label:'Évaluation' },
|
||||
{ id:'documents', label:'Documents', count: candidate.documents?.length },
|
||||
{ id:'tests', label:'Tests', count: candidate.attempts?.length },
|
||||
]" :key="tab.id" @click="activeTab = tab.id"
|
||||
{ id:'security', label:'Sécurité', count: candidate.user.security_alerts?.length },
|
||||
].filter(t => t.id !== 'security' || t.count > 0)" :key="tab.id" @click="activeTab = tab.id"
|
||||
class="relative flex items-center gap-2 px-5 py-4 text-[11px] font-black uppercase tracking-[0.1em] whitespace-nowrap transition-all duration-150"
|
||||
:class="activeTab === tab.id ? 'text-primary' : 'text-ink/35 hover:text-ink/60'">
|
||||
{{ tab.label }}
|
||||
@@ -773,6 +790,55 @@ const barColor = (pct) => pct >= 80 ? 'bg-success' : pct >= 60 ? 'bg-highlight'
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Tab: Sécurité ── -->
|
||||
<div v-if="activeTab === 'security'" class="p-6 bg-accent/[0.02]">
|
||||
<div class="mb-6 flex items-center justify-between">
|
||||
<div>
|
||||
<h3 class="text-lg font-serif font-black text-accent flex items-center gap-2">
|
||||
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
|
||||
Alertes de Sécurité
|
||||
</h3>
|
||||
<p class="text-xs text-ink/50 mt-1 font-semibold">Le candidat a déclenché un ou plusieurs honeypots sur la plateforme.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div v-for="alert in candidate.user.security_alerts" :key="alert.id" class="p-5 rounded-2xl border border-accent/20 bg-white shadow-sm overflow-hidden relative group">
|
||||
<div class="absolute left-0 top-0 bottom-0 w-1.5 bg-accent"></div>
|
||||
|
||||
<div class="flex items-center justify-between mb-4 pl-3">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="px-2.5 py-1 rounded bg-accent/10 text-accent text-[10px] font-black uppercase tracking-widest border border-accent/20">
|
||||
{{ alert.type.replace('_', ' ') }}
|
||||
</span>
|
||||
<span class="text-xs font-bold text-ink/70 flex items-center gap-1.5">
|
||||
<svg class="w-3.5 h-3.5 text-ink/30" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
|
||||
{{ formatDateTime(alert.created_at) }}
|
||||
</span>
|
||||
</div>
|
||||
<span class="text-[10px] font-black font-mono text-ink/40 bg-ink/5 px-2 py-0.5 rounded">{{ alert.ip_address }}</span>
|
||||
</div>
|
||||
|
||||
<div class="pl-3 space-y-3">
|
||||
<div>
|
||||
<p class="text-[9px] font-black uppercase tracking-[0.16em] text-ink/35 mb-1">Endpoint Visé</p>
|
||||
<p class="text-xs font-mono font-bold text-ink/80 bg-neutral/50 px-3 py-2 rounded-lg border border-ink/5 inline-block">
|
||||
{{ alert.endpoint || 'Inconnu' }}
|
||||
</p>
|
||||
</div>
|
||||
<div v-if="alert.payload && Object.keys(alert.payload).length > 0">
|
||||
<p class="text-[9px] font-black uppercase tracking-[0.16em] text-ink/35 mb-1">Payload / Paramètres</p>
|
||||
<pre class="text-[10px] text-ink/70 font-mono font-semibold bg-surface border border-ink/10 p-3 rounded-lg overflow-x-auto">{{ JSON.stringify(alert.payload, null, 2) }}</pre>
|
||||
</div>
|
||||
<div v-if="alert.user_agent">
|
||||
<p class="text-[9px] font-black uppercase tracking-[0.16em] text-ink/35 mb-1">Navigateur (User Agent)</p>
|
||||
<p class="text-[10px] text-ink/50 bg-neutral/30 px-3 py-2 rounded-lg truncate" :title="alert.user_agent">{{ alert.user_agent }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div><!-- end tabs card -->
|
||||
</div><!-- end right panel -->
|
||||
</div><!-- end flex layout -->
|
||||
|
||||
@@ -73,6 +73,13 @@ const addRequirement = () => {
|
||||
const removeRequirement = (index) => {
|
||||
form.requirements.splice(index, 1);
|
||||
};
|
||||
|
||||
const copyLink = (position) => {
|
||||
const url = route('jobs.show', position.id);
|
||||
navigator.clipboard.writeText(url).then(() => {
|
||||
alert('Lien copié dans le presse-papier!');
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -128,14 +135,26 @@ const removeRequirement = (index) => {
|
||||
|
||||
<div class="pt-6 border-t border-slate-100 dark:border-slate-700 flex justify-between gap-3">
|
||||
<SecondaryButton @click="openModal(position)" class="flex-1 !justify-center !py-2 text-xs">Modifier</SecondaryButton>
|
||||
<button
|
||||
@click="deletePosition(position.id)"
|
||||
class="p-2 text-slate-400 hover:text-red-500 hover:bg-red-50 dark:hover:bg-red-900/20 rounded-xl transition-all"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||
</svg>
|
||||
</button>
|
||||
<div class="flex gap-1">
|
||||
<button
|
||||
@click="copyLink(position)"
|
||||
title="Copier le lien de candidature"
|
||||
class="p-2 text-slate-400 hover:text-indigo-500 hover:bg-indigo-50 dark:hover:bg-indigo-900/20 rounded-xl transition-all"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1" />
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
@click="deletePosition(position.id)"
|
||||
title="Supprimer"
|
||||
class="p-2 text-slate-400 hover:text-red-500 hover:bg-red-50 dark:hover:bg-red-900/20 rounded-xl transition-all"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user