Compare commits
3 Commits
ac0bc8d1b2
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| addfb5ad38 | |||
| 88dfa74663 | |||
|
|
2ff097b648 |
@@ -57,7 +57,7 @@ export function C2TasksPanel({ simulationId }: C2TasksPanelProps): JSX.Element {
|
||||
<table className="w-full text-[14px]">
|
||||
<thead>
|
||||
<tr className="bg-cloud border-b border-hairline">
|
||||
<th className="px-md py-sm text-left font-medium text-ink w-8" aria-label="Expand" />
|
||||
<th className="px-md py-sm text-left font-medium text-ink w-8" aria-label={t('c2.tasks.col.expand')} />
|
||||
<th className="px-md py-sm text-left font-medium text-ink">{t('c2.tasks.col.task')}</th>
|
||||
<th className="px-md py-sm text-left font-medium text-ink">{t('c2.tasks.col.command')}</th>
|
||||
<th className="px-md py-sm text-left font-medium text-ink">{t('c2.tasks.col.source')}</th>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { ChevronDown, Download } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { downloadEngagementExport, type ExportFormat } from '@/api/exports';
|
||||
import { useToast } from '@/hooks/useToast';
|
||||
|
||||
@@ -14,6 +15,7 @@ const FORMATS: { label: string; value: ExportFormat }[] = [
|
||||
];
|
||||
|
||||
export function ExportEngagementButton({ engagementId }: ExportEngagementButtonProps): JSX.Element {
|
||||
const { t } = useTranslation();
|
||||
const { push } = useToast();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [loading, setLoading] = useState<ExportFormat | null>(null);
|
||||
@@ -58,11 +60,11 @@ export function ExportEngagementButton({ engagementId }: ExportEngagementButtonP
|
||||
onClick={() => setOpen((v) => !v)}
|
||||
data-testid="export-btn"
|
||||
>
|
||||
<Download size={14} aria-hidden /> Export
|
||||
<Download size={14} aria-hidden /> {t('export.btn')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Export options"
|
||||
aria-label={t('export.aria')}
|
||||
aria-expanded={open}
|
||||
className="btn-outline px-sm"
|
||||
onClick={() => setOpen((v) => !v)}
|
||||
@@ -89,7 +91,7 @@ export function ExportEngagementButton({ engagementId }: ExportEngagementButtonP
|
||||
>
|
||||
{loading === value ? (
|
||||
<span className="font-mono text-[11px] animate-pulse" aria-hidden>
|
||||
EXPORTING…
|
||||
{t('export.btnLoading')}
|
||||
</span>
|
||||
) : null}
|
||||
{loading !== value ? label : null}
|
||||
|
||||
@@ -140,7 +140,7 @@ export function ImportC2HistoryModal({
|
||||
<table className="w-full text-[14px]">
|
||||
<thead>
|
||||
<tr className="bg-cloud border-b border-hairline">
|
||||
<th className="px-md py-sm w-8" aria-label="Select" />
|
||||
<th className="px-md py-sm w-8" aria-label={t('common.select')} />
|
||||
<th className="px-md py-sm text-left font-medium text-ink">#</th>
|
||||
<th className="px-md py-sm text-left font-medium text-ink">{t('c2.tasks.col.command')}</th>
|
||||
<th className="px-md py-sm text-left font-medium text-ink">{t('c2.tasks.col.status')}</th>
|
||||
|
||||
@@ -43,7 +43,7 @@ export function Layout(): JSX.Element {
|
||||
<button
|
||||
type="button"
|
||||
onClick={cycleTheme}
|
||||
aria-label={`Theme: ${themeLabel(theme)} — click to cycle`}
|
||||
aria-label={t('nav.themeAria', { label: themeLabel(theme) })}
|
||||
className="flex items-center gap-xxs text-[12px] text-slab-muted hover:text-slab-text"
|
||||
>
|
||||
<ThemeIcon theme={theme} />
|
||||
@@ -64,7 +64,7 @@ export function Layout(): JSX.Element {
|
||||
{/* nav-bar-top — fixed dark slab, never inverts (same visual family as utility-strip + footer) */}
|
||||
<header className="bg-slab text-slab-text">
|
||||
<div className="mx-auto w-full max-w-page px-xl h-16 flex items-center justify-between">
|
||||
<Link to="/engagements" className="flex items-center gap-sm" aria-label="Mimic home">
|
||||
<Link to="/engagements" className="flex items-center gap-sm" aria-label={t('nav.brandHome')}>
|
||||
<span className="inline-block h-6 w-6 rotate-12 bg-primary" aria-hidden />
|
||||
<span className="text-[20px] font-medium tracking-tight text-slab-text">Mimic</span>
|
||||
</Link>
|
||||
|
||||
@@ -278,7 +278,7 @@ export function MitreMatrixModal({
|
||||
{hasSubtechniques ? (
|
||||
<button
|
||||
type="button"
|
||||
aria-label={isExpanded ? `Collapse ${tech.id}` : `Expand ${tech.id}`}
|
||||
aria-label={isExpanded ? t('common.collapse', { id: tech.id }) : t('common.expand', { id: tech.id })}
|
||||
onClick={() => toggleExpand(tech.id)}
|
||||
className={`mr-[2px] flex-shrink-0 text-[9px] w-3 leading-none mt-[1px] ${
|
||||
isSelected ? 'text-white' : 'text-graphite'
|
||||
@@ -329,7 +329,7 @@ export function MitreMatrixModal({
|
||||
);
|
||||
})}
|
||||
{visibleTechniques.length === 0 && searchLower && (
|
||||
<div className="px-xs py-xxs text-[10px] text-graphite italic">No match</div>
|
||||
<div className="px-xs py-xxs text-[10px] text-graphite italic">{t('mitre.matrix.noMatch')}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -79,7 +79,7 @@ function NewSimulationDropdown({ engagementId }: { engagementId: number }): JSX.
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="More options"
|
||||
aria-label={t('simulation.list.rowActions')}
|
||||
aria-expanded={open}
|
||||
className="btn-primary px-sm"
|
||||
onClick={() => setOpen((v) => !v)}
|
||||
|
||||
@@ -16,7 +16,10 @@
|
||||
"close": "Fermer",
|
||||
"confirm": "Confirmer",
|
||||
"yes": "Oui",
|
||||
"no": "Non"
|
||||
"no": "Non",
|
||||
"expand": "Déplier {{id}}",
|
||||
"collapse": "Replier {{id}}",
|
||||
"select": "Sélectionner"
|
||||
},
|
||||
"nav": {
|
||||
"engagements": "Engagements",
|
||||
@@ -24,8 +27,15 @@
|
||||
"users": "Utilisateurs",
|
||||
"signOut": "Se déconnecter",
|
||||
"brand": "Mimic",
|
||||
"brandHome": "Accueil Mimic",
|
||||
"themeAria": "Thème : {{label}} — cliquer pour changer",
|
||||
"footer": "Mimic — Outillage interne Purple Team. Engagements autorisés uniquement."
|
||||
},
|
||||
"export": {
|
||||
"btn": "Exporter",
|
||||
"btnLoading": "Export en cours…",
|
||||
"aria": "Options d'export"
|
||||
},
|
||||
"auth": {
|
||||
"login": {
|
||||
"title": "Mimic",
|
||||
@@ -208,7 +218,8 @@
|
||||
"desc": "Créez votre première simulation pour cet engagement."
|
||||
},
|
||||
"new": "Nouvelle simulation",
|
||||
"error": "Impossible de charger les simulations"
|
||||
"error": "Impossible de charger les simulations",
|
||||
"rowActions": "Plus d'options"
|
||||
}
|
||||
},
|
||||
"template": {
|
||||
@@ -382,7 +393,8 @@
|
||||
"command": "Commande",
|
||||
"source": "Source",
|
||||
"status": "Statut",
|
||||
"completedAt": "Terminé le"
|
||||
"completedAt": "Terminé le",
|
||||
"expand": "Déplier"
|
||||
},
|
||||
"refreshing": "Actualisation…"
|
||||
},
|
||||
@@ -464,7 +476,8 @@
|
||||
"applyItem_other": "Appliquer ({{count}} éléments)",
|
||||
"clearAll": "Tout effacer",
|
||||
"close": "Fermer",
|
||||
"retry": "Réessayer"
|
||||
"retry": "Réessayer",
|
||||
"noMatch": "Aucun résultat"
|
||||
},
|
||||
"field": {
|
||||
"empty": "Aucune technique associée.",
|
||||
|
||||
@@ -47,7 +47,7 @@ describe('ExportEngagementButton', () => {
|
||||
renderWithProviders(<ExportEngagementButton engagementId={1} />);
|
||||
expect(screen.getByTestId('export-btn')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('export-dropdown-toggle')).toBeInTheDocument();
|
||||
expect(screen.getByText('Export')).toBeInTheDocument();
|
||||
expect(screen.getByText('Exporter')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('clicking primary opens dropdown with three formats', async () => {
|
||||
|
||||
@@ -224,7 +224,7 @@ describe('MitreMatrixModal', () => {
|
||||
await waitFor(() => screen.getByText('T1078'));
|
||||
expect(screen.queryByText(/Default Accounts/)).toBeNull();
|
||||
|
||||
const expandBtn = screen.getByRole('button', { name: /Expand T1078/i });
|
||||
const expandBtn = screen.getByRole('button', { name: /Déplier T1078/i });
|
||||
await user.click(expandBtn);
|
||||
|
||||
expect(screen.getByText(/Default Accounts/)).toBeInTheDocument();
|
||||
|
||||
Reference in New Issue
Block a user