Files
Investidor-V2/resources/js/pages/profiles/installation.tsx
T
nala 287e991486
tests / ci (push) Waiting to run
oportunidades 7
2026-10-13 11:40:29 +02:00

204 lines
8.2 KiB
TypeScript

import { createOperationKey } from '@/lib/operation-key';
import { useState } from 'react';
import { Management, button, control } from '@/pages/management/shared';
import { profileJson } from '@/lib/profile-json';
type Plan = {
installed: boolean;
consistent: boolean;
ready: boolean;
target: { database: string; port: number };
hash: string;
dependencies: Record<string, boolean>;
changes: string[];
backupDate: string | null;
rollback: string;
bootstrap: {
initialized: boolean;
holders: number[];
version: number | null;
rule: string;
pending: {approval_id:string;confirmation_url:string;expires_at:string} | null;
};
};
export default function Installation(initial: Plan) {
const [plan, setPlan] = useState(initial);
const [reason, setReason] = useState('');
const [confirmed, setConfirmed] = useState(false);
const [first, setFirst] = useState('');
const [second, setSecond] = useState('');
const [busy, setBusy] = useState(false);
const [message, setMessage] = useState('');
const [confirmationUrl, setConfirmationUrl] = useState<string | null>(null);
const [installationKey] = useState(createOperationKey);
const [bootstrapKey] = useState(createOperationKey);
async function run(bootstrap = false) {
setBusy(true);
setMessage('');
try {
const result = await profileJson(
'/management/profile-install' + (bootstrap ? '/bootstrap' : ''),
{
hash: plan.hash,
confirm: confirmed,
reason,
idempotency_key: bootstrap ? bootstrapKey : installationKey,
...(bootstrap
? {
first_user_id: Number(first),
second_user_id: Number(second),
}
: {}),
},
);
setMessage(
bootstrap
? 'Pedido registado, sem atribuir privilégios. O segundo titular deve autenticar-se com MFA e confirmar pelo endereço abaixo.'
: result.changed
? 'Schema instalado. Perfis ainda não disponibilizados; nenhuma conta ou atribuição criada.'
: 'Schema já instalado. Nenhuma reinstalação ou duplicação.',
);
if (bootstrap) setConfirmationUrl(result.confirmation_url as string);
if (!bootstrap)
setPlan(
(await profileJson(
'/management/profile-install',
{},
'GET',
)) as Plan,
);
setConfirmed(false);
} catch (error) {
setMessage(
error instanceof Error
? error.message
: 'Resposta indisponível; consulte o estado antes de repetir.',
);
} finally {
setBusy(false);
}
}
return (
<Management title="Instalação dos perfis — 040000">
<p>
Base {plan.target.database}:{plan.target.port} ·{' '}
{plan.installed ? 'Schema instalado' : 'Schema por instalar'}
</p>
<p>
Preparação:{' '}
{plan.ready
? 'Provas atuais verificadas'
: 'Dependências ou provas por completar'}
. Preparação não significa instalação ou disponibilização dos
perfis.
</p>
{!plan.consistent && (
<p role="alert">
Ledger e schema divergem. Recuperar a consistência antes de
instalar.
</p>
)}
<ul>
{Object.entries(plan.dependencies).map(([key, ok]) => (
<li key={key}>
{key}: {ok ? 'presente' : 'pendente'}
</li>
))}
</ul>
<ul>
{plan.changes.map((c) => (
<li key={c}>{c}</li>
))}
</ul>
<p>Backup/restauro: {plan.backupDate ?? 'Prova atual pendente'}</p>
<p>{plan.rollback}</p>
<label className="block">
Fundamento
<textarea
className={control}
value={reason}
minLength={20}
maxLength={1000}
onChange={(e) => setReason(e.target.value)}
/>
</label>
<label className="block">
<input
type="checkbox"
checked={confirmed}
onChange={(e) => setConfirmed(e.target.checked)}
/>{' '}
Confirmo exclusivamente a ação indicada nesta base
</label>
<button
className={button}
disabled={
busy ||
!confirmed ||
!plan.ready ||
plan.installed ||
!plan.consistent ||
reason.length < 20
}
onClick={() => void run()}
>
Instalar schema 040000
</button>
<section className="space-y-3 rounded border p-4">
<h2>Inicialização separada dos dois superutilizadores</h2>
<p>{plan.bootstrap.rule}</p>
{plan.bootstrap.pending && <p>Pedido pendente até {plan.bootstrap.pending.expires_at}. <a className="underline" href={plan.bootstrap.pending.confirmation_url}>Recuperar pedido de confirmação</a>. Nenhum privilégio foi atribuído pelo pedido.</p>}
{plan.bootstrap.initialized ? (
<p>
Dupla já inicializada:{' '}
{plan.bootstrap.holders.join(' e ')}; versão{' '}
{plan.bootstrap.version}. Use a substituição atómica
autorizada; não reinicialize.
</p>
) : (
<>
<label className="block">
ID da primeira conta explicitamente designada
<input
className={control}
type="number"
min={1}
value={first}
onChange={(e) => setFirst(e.target.value)}
/>
</label>
<label className="block">
ID da segunda conta explicitamente designada
<input
className={control}
type="number"
min={1}
value={second}
onChange={(e) => setSecond(e.target.value)}
/>
</label>
<button
className={button}
disabled={
busy ||
!confirmed ||
!plan.installed ||
!!plan.bootstrap.pending ||
!first ||
!second ||
first === second ||
reason.length < 20
}
onClick={() => void run(true)}
>
Pedir confirmação ao segundo titular
</button>
</>
)}
</section>
<p role="status">{message}</p>
{confirmationUrl && <p>Endereço para o segundo titular: <a className="underline" href={confirmationUrl}>{confirmationUrl}</a>. O primeiro titular pode consultar, mas não confirmar.</p>}
</Management>
);
}