feat(frontend): i18n C2 components (config card, tasks panel, execute/import modals, picker)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { useEffect, useState, type FormEvent } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { extractApiError } from '@/api/client';
|
||||
import { useC2Config, useDeleteC2Config, useTestC2Config, useUpdateC2Config } from '@/hooks/useC2';
|
||||
import { ConfirmDialog } from './ConfirmDialog';
|
||||
@@ -10,6 +11,7 @@ interface C2ConfigCardProps {
|
||||
}
|
||||
|
||||
export function C2ConfigCard({ engagementId }: C2ConfigCardProps): JSX.Element {
|
||||
const { t } = useTranslation();
|
||||
const { push } = useToast();
|
||||
|
||||
const configQuery = useC2Config(engagementId);
|
||||
@@ -55,11 +57,11 @@ export function C2ConfigCard({ engagementId }: C2ConfigCardProps): JSX.Element {
|
||||
|
||||
try {
|
||||
await updateMutation.mutateAsync(input);
|
||||
push('C2 configuration saved', 'success');
|
||||
push(t('c2.config.toast.saved'), 'success');
|
||||
setToken('');
|
||||
setReplaceToken(false);
|
||||
} catch (err) {
|
||||
push(extractApiError(err, 'Could not save C2 configuration'), 'error');
|
||||
push(extractApiError(err, t('c2.config.error.save')), 'error');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -68,13 +70,13 @@ export function C2ConfigCard({ engagementId }: C2ConfigCardProps): JSX.Element {
|
||||
setTestResult(null);
|
||||
try {
|
||||
await deleteMutation.mutateAsync();
|
||||
push('C2 configuration removed', 'success');
|
||||
push(t('c2.config.toast.deleted'), 'success');
|
||||
setUrl('');
|
||||
setToken('');
|
||||
setVerifyTls(false);
|
||||
setReplaceToken(false);
|
||||
} catch (err) {
|
||||
push(extractApiError(err, 'Could not remove C2 configuration'), 'error');
|
||||
push(extractApiError(err, t('c2.config.error.delete')), 'error');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -84,10 +86,10 @@ export function C2ConfigCard({ engagementId }: C2ConfigCardProps): JSX.Element {
|
||||
const result = await testMutation.mutateAsync();
|
||||
setTestResult({
|
||||
ok: result.ok,
|
||||
message: result.ok ? 'Connected' : (result.error ?? 'Connection failed'),
|
||||
message: result.ok ? t('c2.config.connected') : (result.error ?? t('c2.config.connectionFailed')),
|
||||
});
|
||||
} catch (err) {
|
||||
setTestResult({ ok: false, message: extractApiError(err, 'Test failed') });
|
||||
setTestResult({ ok: false, message: extractApiError(err, t('c2.config.connectionFailed')) });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -98,25 +100,25 @@ export function C2ConfigCard({ engagementId }: C2ConfigCardProps): JSX.Element {
|
||||
data-testid="c2-config-card"
|
||||
className="card-product flex flex-col gap-md"
|
||||
>
|
||||
<h2 className="text-[20px] font-medium text-ink">C2 configuration</h2>
|
||||
<h2 className="text-[20px] font-medium text-ink">{t('c2.config.title')}</h2>
|
||||
|
||||
{is503 && (
|
||||
<div
|
||||
role="alert"
|
||||
className="rounded-none px-xl py-md bg-fog border border-hairline text-[14px] text-charcoal"
|
||||
>
|
||||
C2 features are disabled (server has no encryption key configured).
|
||||
{t('c2.config.disabled')}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{configQuery.isLoading ? (
|
||||
<p className="text-[14px] text-graphite">Loading…</p>
|
||||
<p className="text-[14px] text-graphite">{t('c2.config.loading')}</p>
|
||||
) : (
|
||||
<form onSubmit={onSave} noValidate className="flex flex-col gap-md">
|
||||
<FormField
|
||||
label="URL"
|
||||
label={t('c2.config.field.url')}
|
||||
htmlFor="c2-url"
|
||||
hint="HTTPS required (e.g. https://mythic.lab:7443)"
|
||||
hint={t('c2.config.field.urlHint')}
|
||||
>
|
||||
<TextInput
|
||||
id="c2-url"
|
||||
@@ -130,7 +132,7 @@ export function C2ConfigCard({ engagementId }: C2ConfigCardProps): JSX.Element {
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<FormField label="API token" htmlFor="c2-token">
|
||||
<FormField label={t('c2.config.field.token')} htmlFor="c2-token">
|
||||
{config?.has_token && !replaceToken ? (
|
||||
<div className="flex items-center gap-md">
|
||||
<TextInput
|
||||
@@ -149,7 +151,7 @@ export function C2ConfigCard({ engagementId }: C2ConfigCardProps): JSX.Element {
|
||||
onClick={() => setReplaceToken(true)}
|
||||
disabled={disabled}
|
||||
>
|
||||
Replace token
|
||||
{t('c2.config.btn.replaceToken')}
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
@@ -158,7 +160,7 @@ export function C2ConfigCard({ engagementId }: C2ConfigCardProps): JSX.Element {
|
||||
data-testid="c2-token-input"
|
||||
type="password"
|
||||
name="api_token"
|
||||
placeholder={config?.has_token ? 'Enter new token' : 'API token'}
|
||||
placeholder={config?.has_token ? t('c2.config.field.tokenHint') : t('c2.config.field.token')}
|
||||
value={token}
|
||||
onChange={(e) => setToken(e.target.value)}
|
||||
disabled={disabled}
|
||||
@@ -178,12 +180,11 @@ export function C2ConfigCard({ engagementId }: C2ConfigCardProps): JSX.Element {
|
||||
className="h-4 w-4 accent-primary"
|
||||
/>
|
||||
<label htmlFor="c2-verify-tls" className="text-[14px] text-ink">
|
||||
Verify TLS certificate
|
||||
{t('c2.config.field.verifyTls')}
|
||||
</label>
|
||||
</div>
|
||||
<p className="text-[12px] text-graphite">
|
||||
Uncheck only for lab Mythic with self-signed certificates. Disabling
|
||||
verification exposes the API token to MITM.
|
||||
{t('c2.config.field.verifyTlsHint')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -194,7 +195,7 @@ export function C2ConfigCard({ engagementId }: C2ConfigCardProps): JSX.Element {
|
||||
className="btn-primary"
|
||||
disabled={disabled || submitting}
|
||||
>
|
||||
{updateMutation.isPending ? 'Saving…' : 'Save'}
|
||||
{updateMutation.isPending ? t('c2.config.btn.saving') : t('c2.config.btn.save')}
|
||||
</button>
|
||||
|
||||
<button
|
||||
@@ -204,7 +205,7 @@ export function C2ConfigCard({ engagementId }: C2ConfigCardProps): JSX.Element {
|
||||
onClick={onTest}
|
||||
disabled={disabled || testMutation.isPending || !config}
|
||||
>
|
||||
{testMutation.isPending ? 'Testing…' : 'Test connection'}
|
||||
{testMutation.isPending ? t('c2.config.btn.testing') : t('c2.config.btn.test')}
|
||||
</button>
|
||||
|
||||
{testResult !== null && (
|
||||
@@ -223,7 +224,7 @@ export function C2ConfigCard({ engagementId }: C2ConfigCardProps): JSX.Element {
|
||||
onClick={() => setShowDeleteConfirm(true)}
|
||||
disabled={disabled || submitting}
|
||||
>
|
||||
Delete configuration
|
||||
{t('c2.config.btn.delete')}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@@ -232,10 +233,10 @@ export function C2ConfigCard({ engagementId }: C2ConfigCardProps): JSX.Element {
|
||||
|
||||
{showDeleteConfirm && (
|
||||
<ConfirmDialog
|
||||
title="Delete C2 configuration"
|
||||
description="This will remove the C2 configuration for this engagement. The API token will be permanently deleted."
|
||||
confirmLabel="Delete"
|
||||
cancelLabel="Cancel"
|
||||
title={t('c2.config.deleteConfirm.title')}
|
||||
description={t('c2.config.deleteConfirm.desc')}
|
||||
confirmLabel={t('c2.config.deleteConfirm.confirm')}
|
||||
cancelLabel={t('c2.config.deleteConfirm.cancel')}
|
||||
destructive
|
||||
onConfirm={onDelete}
|
||||
onCancel={() => setShowDeleteConfirm(false)}
|
||||
|
||||
Reference in New Issue
Block a user