From ff7e15043cdf97168e27243c98ac7278d4652fbe Mon Sep 17 00:00:00 2001 From: Shankar Date: Sat, 28 Mar 2026 14:28:56 -0400 Subject: [PATCH] fix(gui): wire create modal onSuccess callbacks and fix short-lived profile UX MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- web/src/pages/AgentGroupsPage.tsx | 15 +++++----- web/src/pages/OwnersPage.tsx | 27 ++++++++--------- web/src/pages/PoliciesPage.tsx | 25 ++++++++-------- web/src/pages/ProfilesPage.tsx | 49 +++++++++++++++++++------------ web/src/pages/TeamsPage.tsx | 23 +++++++-------- 5 files changed, 73 insertions(+), 66 deletions(-) diff --git a/web/src/pages/AgentGroupsPage.tsx b/web/src/pages/AgentGroupsPage.tsx index 1dbe9d3..350b03c 100644 --- a/web/src/pages/AgentGroupsPage.tsx +++ b/web/src/pages/AgentGroupsPage.tsx @@ -29,8 +29,7 @@ function CreateAgentGroupModal({ isOpen, onClose, onSuccess, isLoading, error }: const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!name.trim()) return; - try { - await createAgentGroup({ + await createAgentGroup({ name: name.trim(), description: description.trim(), match_os: matchOs.trim() || undefined, @@ -45,11 +44,8 @@ function CreateAgentGroupModal({ isOpen, onClose, onSuccess, isLoading, error }: setMatchArch(''); setMatchIpCidr(''); setMatchVersion(''); - setEnabled(true); - onSuccess(); - } catch (err) { - console.error('Create agent group error:', err); - } + setEnabled(true); + onSuccess(); }; if (!isOpen) return null; @@ -249,7 +245,10 @@ export default function AgentGroupsPage() { setShowCreate(false)} - onSuccess={() => {}} + onSuccess={() => { + queryClient.invalidateQueries({ queryKey: ['agent-groups'] }); + setShowCreate(false); + }} isLoading={createMutation.isPending} error={createMutation.error ? (createMutation.error as Error).message : null} /> diff --git a/web/src/pages/OwnersPage.tsx b/web/src/pages/OwnersPage.tsx index bb0a6b4..d0fb044 100644 --- a/web/src/pages/OwnersPage.tsx +++ b/web/src/pages/OwnersPage.tsx @@ -25,19 +25,15 @@ function CreateOwnerModal({ isOpen, onClose, onSuccess, isLoading, error, teamsD const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!name.trim() || !email.trim()) return; - try { - await createOwner({ - name: name.trim(), - email: email.trim(), - team_id: teamId || undefined, - }); - setName(''); - setEmail(''); - setTeamId(''); - onSuccess(); - } catch (err) { - console.error('Create owner error:', err); - } + await createOwner({ + name: name.trim(), + email: email.trim(), + team_id: teamId || undefined, + }); + setName(''); + setEmail(''); + setTeamId(''); + onSuccess(); }; if (!isOpen) return null; @@ -203,7 +199,10 @@ export default function OwnersPage() { setShowCreate(false)} - onSuccess={() => {}} + onSuccess={() => { + queryClient.invalidateQueries({ queryKey: ['owners'] }); + setShowCreate(false); + }} isLoading={createMutation.isPending} error={createMutation.error ? (createMutation.error as Error).message : null} teamsData={teamsData} diff --git a/web/src/pages/PoliciesPage.tsx b/web/src/pages/PoliciesPage.tsx index 0ed1d3f..483c305 100644 --- a/web/src/pages/PoliciesPage.tsx +++ b/web/src/pages/PoliciesPage.tsx @@ -40,18 +40,14 @@ function CreatePolicyModal({ isOpen, onClose, onSuccess, isLoading, error }: Cre const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!name.trim()) return; - try { - const config = JSON.parse(configStr); - await createPolicy({ name: name.trim(), type, severity, config, enabled }); - setName(''); - setType('key_algorithm'); - setSeverity('medium'); - setConfigStr('{}'); - setEnabled(true); - onSuccess(); - } catch (err) { - console.error('Create policy error:', err); - } + const config = JSON.parse(configStr); + await createPolicy({ name: name.trim(), type, severity, config, enabled }); + setName(''); + setType('key_algorithm'); + setSeverity('medium'); + setConfigStr('{}'); + setEnabled(true); + onSuccess(); }; if (!isOpen) return null; @@ -269,7 +265,10 @@ export default function PoliciesPage() { setShowCreate(false)} - onSuccess={() => {}} + onSuccess={() => { + queryClient.invalidateQueries({ queryKey: ['policies'] }); + setShowCreate(false); + }} isLoading={createMutation.isPending} error={createMutation.error ? (createMutation.error as Error).message : null} /> diff --git a/web/src/pages/ProfilesPage.tsx b/web/src/pages/ProfilesPage.tsx index 570bac4..d0c4e7b 100644 --- a/web/src/pages/ProfilesPage.tsx +++ b/web/src/pages/ProfilesPage.tsx @@ -34,22 +34,18 @@ function CreateProfileModal({ isOpen, onClose, onSuccess, isLoading, error }: Cr const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!name.trim()) return; - try { - await createProfile({ - name: name.trim(), - description: description.trim(), - max_ttl_seconds: parseInt(ttl) || 86400, - allow_short_lived: shortLived, - enabled: true, - }); - setName(''); - setDescription(''); - setTtl('86400'); - setShortLived(false); - onSuccess(); - } catch (err) { - console.error('Create profile error:', err); - } + await createProfile({ + name: name.trim(), + description: description.trim(), + max_ttl_seconds: parseInt(ttl) || 86400, + allow_short_lived: shortLived, + enabled: true, + }); + setName(''); + setDescription(''); + setTtl('86400'); + setShortLived(false); + onSuccess(); }; 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" placeholder="86400" /> -

e.g. 86400 = 1 day, 2592000 = 30 days

+

+ {shortLived + ? 'Short-lived certs require TTL under 3600 (1 hour). e.g. 300 = 5m, 1800 = 30m' + : 'e.g. 86400 = 1 day, 2592000 = 30 days'} +

+ {shortLived && parseInt(ttl) >= 3600 && ( +

TTL must be under 3600 for short-lived certs

+ )}
setShortLived(e.target.checked)} + onChange={e => { + setShortLived(e.target.checked); + if (e.target.checked && parseInt(ttl) >= 3600) { + setTtl('300'); + } + }} className="w-4 h-4" /> @@ -251,7 +259,10 @@ export default function ProfilesPage() { setShowCreate(false)} - onSuccess={() => {}} + onSuccess={() => { + queryClient.invalidateQueries({ queryKey: ['profiles'] }); + setShowCreate(false); + }} isLoading={createMutation.isPending} error={createMutation.error ? (createMutation.error as Error).message : null} /> diff --git a/web/src/pages/TeamsPage.tsx b/web/src/pages/TeamsPage.tsx index c3f72a4..c6106e7 100644 --- a/web/src/pages/TeamsPage.tsx +++ b/web/src/pages/TeamsPage.tsx @@ -23,17 +23,13 @@ function CreateTeamModal({ isOpen, onClose, onSuccess, isLoading, error }: Creat const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!name.trim()) return; - try { - await createTeam({ - name: name.trim(), - description: description.trim(), - }); - setName(''); - setDescription(''); - onSuccess(); - } catch (err) { - console.error('Create team error:', err); - } + await createTeam({ + name: name.trim(), + description: description.trim(), + }); + setName(''); + setDescription(''); + onSuccess(); }; if (!isOpen) return null; @@ -167,7 +163,10 @@ export default function TeamsPage() { setShowCreate(false)} - onSuccess={() => {}} + onSuccess={() => { + queryClient.invalidateQueries({ queryKey: ['teams'] }); + setShowCreate(false); + }} isLoading={createMutation.isPending} error={createMutation.error ? (createMutation.error as Error).message : null} />