source: advance the checked projection
Source-Sha: 194a59adfd6c901635da7a9c18253bb2210e2426 Policy-Sha: b9b0be0ee74e55f561a803b8aef6be3c2134f5a83b46816a069a372f2eb04f4d Tree-Digest: 4697f2e281c1c6ecafff17cf6eeb391a0a1ac0fa6e5fc429f5b473f4579c252c
This commit is contained in:
parent
0401c76c79
commit
ffdccba10f
42 changed files with 2516 additions and 554 deletions
|
|
@ -204,13 +204,15 @@ const AgentsEnrollment: React.FC = () => {
|
|||
const [installPlatform, setInstallPlatform] = useState<string>('linux');
|
||||
const [installTokenId, setInstallTokenId] = useState<string>('');
|
||||
const [copiedCommand, setCopiedCommand] = useState<string | null>(null);
|
||||
const [createdToken, setCreatedToken] = useState<{ token: string; label: string } | null>(null);
|
||||
const [createdToken, setCreatedToken] = useState<{ token: string; label: string; fleetJoin: boolean } | null>(null);
|
||||
|
||||
// Create-key form
|
||||
const [formData, setFormData] = useState<CreateRegistrationTokenRequest>({
|
||||
label: '',
|
||||
expires_in: '168h',
|
||||
max_seats: 1,
|
||||
fleet_join: false,
|
||||
totp_seed: '',
|
||||
});
|
||||
|
||||
// Signing keys (preserved from the old Agent Management page — now its own
|
||||
|
|
@ -249,7 +251,7 @@ const AgentsEnrollment: React.FC = () => {
|
|||
const availableTokens = React.useMemo(
|
||||
() =>
|
||||
allTokens.filter(
|
||||
(t) => !t.revoked && t.status === 'active' && t.seats_used < t.max_seats,
|
||||
(t) => !t.revoked && t.status === 'active' && t.seats_used < t.max_seats && !t.metadata?.fleet_join,
|
||||
),
|
||||
[allTokens],
|
||||
);
|
||||
|
|
@ -293,11 +295,17 @@ const AgentsEnrollment: React.FC = () => {
|
|||
|
||||
const handleCreateToken = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
createToken.mutate(formData, {
|
||||
const request: CreateRegistrationTokenRequest = {
|
||||
...formData,
|
||||
max_seats: formData.fleet_join ? 1 : formData.max_seats,
|
||||
totp_seed: formData.fleet_join ? formData.totp_seed?.trim().toUpperCase() : undefined,
|
||||
};
|
||||
createToken.mutate(request, {
|
||||
onSuccess: (data: any) => {
|
||||
const label = formData.label || data.label;
|
||||
setFormData({ label: '', expires_in: '168h', max_seats: 1 });
|
||||
setCreatedToken({ token: data.token, label });
|
||||
const fleetJoin = Boolean(formData.fleet_join);
|
||||
setFormData({ label: '', expires_in: '168h', max_seats: 1, fleet_join: false, totp_seed: '' });
|
||||
setCreatedToken({ token: data.token, label, fleetJoin });
|
||||
refetch();
|
||||
},
|
||||
});
|
||||
|
|
@ -435,8 +443,10 @@ const AgentsEnrollment: React.FC = () => {
|
|||
<div className="space-y-4">
|
||||
<div className="alert alert-success flex items-start justify-between gap-3">
|
||||
<p className="text-sm text-success-800">
|
||||
<span className="font-medium">Key "{createdToken.label}" created.</span> Copy the
|
||||
token now — it cannot be retrieved again, only a hash is stored.
|
||||
<span className="font-medium">Key "{createdToken.label}" created.</span>{' '}
|
||||
{createdToken.fleetJoin
|
||||
? 'Return to the standalone Desktop and enter this token.'
|
||||
: 'Copy the token now, then choose the target platform and copy its install command.'}
|
||||
</p>
|
||||
<button
|
||||
onClick={() => setCreatedToken(null)}
|
||||
|
|
@ -460,13 +470,21 @@ const AgentsEnrollment: React.FC = () => {
|
|||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<PlatformPicker value={installPlatform} onChange={setInstallPlatform} />
|
||||
<InstallCommandBox
|
||||
command={installCommand}
|
||||
platform={installPlatform}
|
||||
copied={copiedCommand === 'install'}
|
||||
onCopy={() => copyToClipboard(installCommand, 'install')}
|
||||
/>
|
||||
{createdToken.fleetJoin ? (
|
||||
<div className="rounded-lg border border-primary-200 bg-primary-50 p-4 text-sm text-primary-900">
|
||||
This one-seat key can join only the standalone Agent whose host seed you entered. It is not an install key.
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<PlatformPicker value={installPlatform} onChange={setInstallPlatform} />
|
||||
<InstallCommandBox
|
||||
command={installCommand}
|
||||
platform={installPlatform}
|
||||
copied={copiedCommand === 'install'}
|
||||
onCopy={() => copyToClipboard(installCommand, 'install')}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
|
|
@ -551,6 +569,18 @@ const AgentsEnrollment: React.FC = () => {
|
|||
)
|
||||
) : (
|
||||
<form onSubmit={handleCreateToken} className="space-y-4">
|
||||
<label className="flex items-start gap-3 rounded-lg border border-gray-200 bg-gray-50 p-4 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={Boolean(formData.fleet_join)}
|
||||
onChange={(e) => setFormData({ ...formData, fleet_join: e.target.checked, max_seats: e.target.checked ? 1 : formData.max_seats })}
|
||||
className="mt-1"
|
||||
/>
|
||||
<span>
|
||||
<span className="block text-sm font-medium text-gray-900">Join an existing standalone machine</span>
|
||||
<span className="block text-xs text-gray-500 mt-1">Creates one key for one existing Agent ID. Generate the host seed in RedFlag Desktop first.</span>
|
||||
</span>
|
||||
</label>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">Label *</label>
|
||||
|
|
@ -584,13 +614,29 @@ const AgentsEnrollment: React.FC = () => {
|
|||
type="number"
|
||||
min="1"
|
||||
max="100"
|
||||
value={formData.max_seats || 1}
|
||||
value={formData.fleet_join ? 1 : formData.max_seats || 1}
|
||||
disabled={Boolean(formData.fleet_join)}
|
||||
onChange={(e) => setFormData({ ...formData, max_seats: parseInt(e.target.value) || 1 })}
|
||||
className="form-input"
|
||||
/>
|
||||
<p className="mt-1 text-xs text-gray-500">Number of agents that can enroll with this key</p>
|
||||
<p className="mt-1 text-xs text-gray-500">{formData.fleet_join ? 'Fleet join keys always have one seat' : 'Number of agents that can enroll with this key'}</p>
|
||||
</div>
|
||||
</div>
|
||||
{formData.fleet_join && (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">Standalone host seed *</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
value={formData.totp_seed || ''}
|
||||
onChange={(e) => setFormData({ ...formData, totp_seed: e.target.value.toUpperCase().replace(/\s/g, '') })}
|
||||
placeholder="Paste the seed shown by RedFlag Desktop"
|
||||
className="form-input font-mono"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<button type="submit" disabled={createToken.isPending} className="btn-primary">
|
||||
{createToken.isPending ? 'Creating...' : 'Create key'}
|
||||
</button>
|
||||
|
|
@ -690,6 +736,7 @@ const AgentsEnrollment: React.FC = () => {
|
|||
<div className="mt-1 flex items-center gap-1.5 text-xs text-gray-500">
|
||||
<span className={cn('inline-block w-2 h-2 rounded-full', STATUS_DOT[t.status] || 'bg-gray-400')} />
|
||||
<span>{getStatusText(t)}</span>
|
||||
{t.metadata?.fleet_join && <span className="text-primary-700 font-medium">Fleet join</span>}
|
||||
<span>·</span>
|
||||
<span>
|
||||
{t.seats_used}/{t.max_seats} seats
|
||||
|
|
@ -728,10 +775,13 @@ const AgentsEnrollment: React.FC = () => {
|
|||
<span className={cn('badge badge-lg', tokenStatusColor(selectedToken.status))}>
|
||||
{getStatusText(selectedToken)}
|
||||
</span>
|
||||
{selectedToken.metadata?.fleet_join && (
|
||||
<span className="badge badge-lg ml-2 bg-primary-100 text-primary-800">Fleet join</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 shrink-0">
|
||||
{selectedToken.status === 'active' && selectedToken.seats_used < selectedToken.max_seats && (
|
||||
{selectedToken.status === 'active' && selectedToken.seats_used < selectedToken.max_seats && !selectedToken.metadata?.fleet_join && (
|
||||
<button
|
||||
onClick={() => installWithKey(selectedToken.id)}
|
||||
className="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm text-primary-700 bg-primary-50 border border-primary-200 rounded-md hover:bg-primary-100"
|
||||
|
|
|
|||
|
|
@ -525,6 +525,8 @@ export interface CreateRegistrationTokenRequest {
|
|||
expires_in?: string;
|
||||
max_seats?: number;
|
||||
metadata?: Record<string, any>;
|
||||
fleet_join?: boolean;
|
||||
totp_seed?: string;
|
||||
}
|
||||
|
||||
export interface RegistrationTokenStats {
|
||||
|
|
|
|||
Loading…
Reference in a new issue