Compare commits

..

1 Commits

Author SHA1 Message Date
shankar0123 78c7bc16b0 fix(gui): wire create modal onSuccess callbacks and fix short-lived profile UX
- All 5 create modals (Profiles, Teams, Owners, Policies, Agent Groups)
  had no-op onSuccess callbacks — API call fired but modal never closed
  and list never refreshed. Wired invalidateQueries + setShowCreate.
- Removed silent try/catch error swallowing so API errors surface in UI.
- Profile create: auto-set TTL to 300s when short-lived checkbox enabled
  with TTL >= 3600, added validation hint and warning text.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-28 14:28:56 -04:00
5 changed files with 73 additions and 66 deletions
+7 -8
View File
@@ -29,8 +29,7 @@ function CreateAgentGroupModal({ isOpen, onClose, onSuccess, isLoading, error }:
const handleSubmit = async (e: React.FormEvent) => { const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
if (!name.trim()) return; if (!name.trim()) return;
try { await createAgentGroup({
await createAgentGroup({
name: name.trim(), name: name.trim(),
description: description.trim(), description: description.trim(),
match_os: matchOs.trim() || undefined, match_os: matchOs.trim() || undefined,
@@ -45,11 +44,8 @@ function CreateAgentGroupModal({ isOpen, onClose, onSuccess, isLoading, error }:
setMatchArch(''); setMatchArch('');
setMatchIpCidr(''); setMatchIpCidr('');
setMatchVersion(''); setMatchVersion('');
setEnabled(true); setEnabled(true);
onSuccess(); onSuccess();
} catch (err) {
console.error('Create agent group error:', err);
}
}; };
if (!isOpen) return null; if (!isOpen) return null;
@@ -249,7 +245,10 @@ export default function AgentGroupsPage() {
<CreateAgentGroupModal <CreateAgentGroupModal
isOpen={showCreate} isOpen={showCreate}
onClose={() => setShowCreate(false)} onClose={() => setShowCreate(false)}
onSuccess={() => {}} onSuccess={() => {
queryClient.invalidateQueries({ queryKey: ['agent-groups'] });
setShowCreate(false);
}}
isLoading={createMutation.isPending} isLoading={createMutation.isPending}
error={createMutation.error ? (createMutation.error as Error).message : null} error={createMutation.error ? (createMutation.error as Error).message : null}
/> />
+13 -14
View File
@@ -25,19 +25,15 @@ function CreateOwnerModal({ isOpen, onClose, onSuccess, isLoading, error, teamsD
const handleSubmit = async (e: React.FormEvent) => { const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
if (!name.trim() || !email.trim()) return; if (!name.trim() || !email.trim()) return;
try { await createOwner({
await createOwner({ name: name.trim(),
name: name.trim(), email: email.trim(),
email: email.trim(), team_id: teamId || undefined,
team_id: teamId || undefined, });
}); setName('');
setName(''); setEmail('');
setEmail(''); setTeamId('');
setTeamId(''); onSuccess();
onSuccess();
} catch (err) {
console.error('Create owner error:', err);
}
}; };
if (!isOpen) return null; if (!isOpen) return null;
@@ -203,7 +199,10 @@ export default function OwnersPage() {
<CreateOwnerModal <CreateOwnerModal
isOpen={showCreate} isOpen={showCreate}
onClose={() => setShowCreate(false)} onClose={() => setShowCreate(false)}
onSuccess={() => {}} onSuccess={() => {
queryClient.invalidateQueries({ queryKey: ['owners'] });
setShowCreate(false);
}}
isLoading={createMutation.isPending} isLoading={createMutation.isPending}
error={createMutation.error ? (createMutation.error as Error).message : null} error={createMutation.error ? (createMutation.error as Error).message : null}
teamsData={teamsData} teamsData={teamsData}
+12 -13
View File
@@ -40,18 +40,14 @@ function CreatePolicyModal({ isOpen, onClose, onSuccess, isLoading, error }: Cre
const handleSubmit = async (e: React.FormEvent) => { const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
if (!name.trim()) return; if (!name.trim()) return;
try { const config = JSON.parse(configStr);
const config = JSON.parse(configStr); await createPolicy({ name: name.trim(), type, severity, config, enabled });
await createPolicy({ name: name.trim(), type, severity, config, enabled }); setName('');
setName(''); setType('key_algorithm');
setType('key_algorithm'); setSeverity('medium');
setSeverity('medium'); setConfigStr('{}');
setConfigStr('{}'); setEnabled(true);
setEnabled(true); onSuccess();
onSuccess();
} catch (err) {
console.error('Create policy error:', err);
}
}; };
if (!isOpen) return null; if (!isOpen) return null;
@@ -269,7 +265,10 @@ export default function PoliciesPage() {
<CreatePolicyModal <CreatePolicyModal
isOpen={showCreate} isOpen={showCreate}
onClose={() => setShowCreate(false)} onClose={() => setShowCreate(false)}
onSuccess={() => {}} onSuccess={() => {
queryClient.invalidateQueries({ queryKey: ['policies'] });
setShowCreate(false);
}}
isLoading={createMutation.isPending} isLoading={createMutation.isPending}
error={createMutation.error ? (createMutation.error as Error).message : null} error={createMutation.error ? (createMutation.error as Error).message : null}
/> />
+30 -19
View File
@@ -34,22 +34,18 @@ function CreateProfileModal({ isOpen, onClose, onSuccess, isLoading, error }: Cr
const handleSubmit = async (e: React.FormEvent) => { const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
if (!name.trim()) return; if (!name.trim()) return;
try { await createProfile({
await createProfile({ name: name.trim(),
name: name.trim(), description: description.trim(),
description: description.trim(), max_ttl_seconds: parseInt(ttl) || 86400,
max_ttl_seconds: parseInt(ttl) || 86400, allow_short_lived: shortLived,
allow_short_lived: shortLived, enabled: true,
enabled: true, });
}); setName('');
setName(''); setDescription('');
setDescription(''); setTtl('86400');
setTtl('86400'); setShortLived(false);
setShortLived(false); onSuccess();
onSuccess();
} catch (err) {
console.error('Create profile error:', err);
}
}; };
if (!isOpen) return null; if (!isOpen) return null;
@@ -89,14 +85,26 @@ function CreateProfileModal({ isOpen, onClose, onSuccess, isLoading, error }: Cr
className="w-full bg-white border border-surface-border rounded px-3 py-2 text-sm text-ink focus:outline-none focus:border-brand-400" className="w-full bg-white border border-surface-border rounded px-3 py-2 text-sm text-ink focus:outline-none focus:border-brand-400"
placeholder="86400" placeholder="86400"
/> />
<p className="text-xs text-ink-muted mt-1">e.g. 86400 = 1 day, 2592000 = 30 days</p> <p className="text-xs text-ink-muted mt-1">
{shortLived
? 'Short-lived certs require TTL under 3600 (1 hour). e.g. 300 = 5m, 1800 = 30m'
: 'e.g. 86400 = 1 day, 2592000 = 30 days'}
</p>
{shortLived && parseInt(ttl) >= 3600 && (
<p className="text-xs text-amber-600 mt-1">TTL must be under 3600 for short-lived certs</p>
)}
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<input <input
type="checkbox" type="checkbox"
id="shortLived" id="shortLived"
checked={shortLived} checked={shortLived}
onChange={e => setShortLived(e.target.checked)} onChange={e => {
setShortLived(e.target.checked);
if (e.target.checked && parseInt(ttl) >= 3600) {
setTtl('300');
}
}}
className="w-4 h-4" className="w-4 h-4"
/> />
<label htmlFor="shortLived" className="text-sm text-ink">Allow short-lived certs</label> <label htmlFor="shortLived" className="text-sm text-ink">Allow short-lived certs</label>
@@ -251,7 +259,10 @@ export default function ProfilesPage() {
<CreateProfileModal <CreateProfileModal
isOpen={showCreate} isOpen={showCreate}
onClose={() => setShowCreate(false)} onClose={() => setShowCreate(false)}
onSuccess={() => {}} onSuccess={() => {
queryClient.invalidateQueries({ queryKey: ['profiles'] });
setShowCreate(false);
}}
isLoading={createMutation.isPending} isLoading={createMutation.isPending}
error={createMutation.error ? (createMutation.error as Error).message : null} error={createMutation.error ? (createMutation.error as Error).message : null}
/> />
+11 -12
View File
@@ -23,17 +23,13 @@ function CreateTeamModal({ isOpen, onClose, onSuccess, isLoading, error }: Creat
const handleSubmit = async (e: React.FormEvent) => { const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
if (!name.trim()) return; if (!name.trim()) return;
try { await createTeam({
await createTeam({ name: name.trim(),
name: name.trim(), description: description.trim(),
description: description.trim(), });
}); setName('');
setName(''); setDescription('');
setDescription(''); onSuccess();
onSuccess();
} catch (err) {
console.error('Create team error:', err);
}
}; };
if (!isOpen) return null; if (!isOpen) return null;
@@ -167,7 +163,10 @@ export default function TeamsPage() {
<CreateTeamModal <CreateTeamModal
isOpen={showCreate} isOpen={showCreate}
onClose={() => setShowCreate(false)} onClose={() => setShowCreate(false)}
onSuccess={() => {}} onSuccess={() => {
queryClient.invalidateQueries({ queryKey: ['teams'] });
setShowCreate(false);
}}
isLoading={createMutation.isPending} isLoading={createMutation.isPending}
error={createMutation.error ? (createMutation.error as Error).message : null} error={createMutation.error ? (createMutation.error as Error).message : null}
/> />