Show bridge errors in connector cards
Display SCIM bridge state directly on Google Workspace and Microsoft 365 connector cards so admins can quickly identify failing bridges. When a bridge is failed, render an inline error callout and surface the latest provisioning event error message to make troubleshooting visible without leaving the connector section. Signed-off-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Bryan FRIMIN <bryan@frimin.fr>
This commit is contained in:
committed by
Bryan Frimin
parent
4d4f4fa526
commit
f02c3b88b7
@@ -23,6 +23,7 @@ import {
|
|||||||
DialogFooter,
|
DialogFooter,
|
||||||
Google,
|
Google,
|
||||||
IconSettingsGear2,
|
IconSettingsGear2,
|
||||||
|
IconWarning,
|
||||||
Input,
|
Input,
|
||||||
useDialogRef,
|
useDialogRef,
|
||||||
useToast,
|
useToast,
|
||||||
@@ -38,9 +39,18 @@ import { useOrganizationId } from "#/hooks/useOrganizationId";
|
|||||||
const googleWorkspaceConnectorFragment = graphql`
|
const googleWorkspaceConnectorFragment = graphql`
|
||||||
fragment GoogleWorkspaceConnectorFragment on SCIMConfiguration {
|
fragment GoogleWorkspaceConnectorFragment on SCIMConfiguration {
|
||||||
id
|
id
|
||||||
|
events(first: 1) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
errorMessage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
bridge {
|
bridge {
|
||||||
id
|
id
|
||||||
type
|
type
|
||||||
|
state
|
||||||
excludedUserNames
|
excludedUserNames
|
||||||
connector {
|
connector {
|
||||||
id
|
id
|
||||||
@@ -83,9 +93,26 @@ export function GoogleWorkspaceConnector(props: {
|
|||||||
const connector = bridge?.connector;
|
const connector = bridge?.connector;
|
||||||
const scimConfigurationId = data?.id;
|
const scimConfigurationId = data?.id;
|
||||||
const bridgeId = bridge?.id;
|
const bridgeId = bridge?.id;
|
||||||
|
|
||||||
const organizationId = useOrganizationId();
|
const organizationId = useOrganizationId();
|
||||||
const { __, dateTimeFormat } = useTranslate();
|
const { __, dateTimeFormat } = useTranslate();
|
||||||
|
const bridgeState = bridge?.state ?? null;
|
||||||
|
const latestBridgeError = data?.events?.edges?.[0]?.node?.errorMessage ?? null;
|
||||||
|
const isBridgeFailed = bridgeState === "FAILED";
|
||||||
|
const isBridgePending = bridgeState === "PENDING";
|
||||||
|
const bridgeStatusBadgeVariant = isBridgeFailed
|
||||||
|
? "danger"
|
||||||
|
: isBridgePending
|
||||||
|
? "warning"
|
||||||
|
: "success";
|
||||||
|
const bridgeStatusLabel = isBridgeFailed
|
||||||
|
? __("Error")
|
||||||
|
: isBridgePending
|
||||||
|
? __("Syncing")
|
||||||
|
: __("Connected");
|
||||||
|
const bridgeErrorMessage = latestBridgeError
|
||||||
|
?? __(
|
||||||
|
"The bridge is currently failing to sync. Check the provisioning event history for more details.",
|
||||||
|
);
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const dialogRef = useDialogRef();
|
const dialogRef = useDialogRef();
|
||||||
const excludedUserNamesDialogRef = useDialogRef();
|
const excludedUserNamesDialogRef = useDialogRef();
|
||||||
@@ -225,118 +252,134 @@ export function GoogleWorkspaceConnector(props: {
|
|||||||
|
|
||||||
// Connected state
|
// Connected state
|
||||||
return (
|
return (
|
||||||
<Card padded className="flex items-center gap-3">
|
<Card padded className="space-y-3">
|
||||||
<div className="w-10 h-10 flex items-center justify-center bg-subtle rounded">
|
<div className="flex items-center gap-3">
|
||||||
<Google className="w-6 h-6" />
|
<div className="w-10 h-10 flex items-center justify-center bg-subtle rounded">
|
||||||
</div>
|
<Google className="w-6 h-6" />
|
||||||
<div className="mr-auto">
|
</div>
|
||||||
<h3 className="font-medium">{__("Google Workspace / Cloud Identity")}</h3>
|
<div className="mr-auto">
|
||||||
<p className="text-sm text-txt-secondary">
|
<h3 className="font-medium">{__("Google Workspace / Cloud Identity")}</h3>
|
||||||
{sprintf(__("Connected on %s"), dateTimeFormat(connector.createdAt))}
|
<p className="text-sm text-txt-secondary">
|
||||||
</p>
|
{sprintf(__("Connected on %s"), dateTimeFormat(connector.createdAt))}
|
||||||
</div>
|
</p>
|
||||||
<Badge variant="success" size="md">
|
</div>
|
||||||
{__("Connected")}
|
<Badge variant={bridgeStatusBadgeVariant} size="md">
|
||||||
</Badge>
|
{bridgeStatusLabel}
|
||||||
<Dialog
|
</Badge>
|
||||||
ref={excludedUserNamesDialogRef}
|
<Dialog
|
||||||
trigger={(
|
ref={excludedUserNamesDialogRef}
|
||||||
<Button variant="secondary">
|
trigger={(
|
||||||
<IconSettingsGear2 size={16} />
|
<Button variant="secondary">
|
||||||
{__("Settings")}
|
<IconSettingsGear2 size={16} />
|
||||||
</Button>
|
{__("Settings")}
|
||||||
)}
|
</Button>
|
||||||
title={__("Google Workspace / Cloud Identity Settings")}
|
)}
|
||||||
className="max-w-lg"
|
title={__("Google Workspace / Cloud Identity Settings")}
|
||||||
>
|
className="max-w-lg"
|
||||||
<DialogContent padded className="space-y-6">
|
>
|
||||||
<div className="space-y-4">
|
<DialogContent padded className="space-y-6">
|
||||||
<div>
|
<div className="space-y-4">
|
||||||
<h4 className="text-sm font-medium">{__("Excluded user names")}</h4>
|
<div>
|
||||||
<p className="text-sm text-txt-secondary mt-1">
|
<h4 className="text-sm font-medium">{__("Excluded user names")}</h4>
|
||||||
{__("Users with these user names will not be synced from Google Workspace / Cloud Identity.")}
|
<p className="text-sm text-txt-secondary mt-1">
|
||||||
</p>
|
{__("Users with these user names will not be synced from Google Workspace / Cloud Identity.")}
|
||||||
</div>
|
</p>
|
||||||
<div className="flex gap-2">
|
</div>
|
||||||
<Input
|
<div className="flex gap-2">
|
||||||
type="text"
|
<Input
|
||||||
value={newUser}
|
type="text"
|
||||||
onChange={e => setNewUser(e.target.value)}
|
value={newUser}
|
||||||
onKeyDown={(e) => {
|
onChange={e => setNewUser(e.target.value)}
|
||||||
if (e.key === "Enter") {
|
onKeyDown={(e) => {
|
||||||
e.preventDefault();
|
if (e.key === "Enter") {
|
||||||
if (isUpdating) return;
|
e.preventDefault();
|
||||||
handleAddUser();
|
if (isUpdating) return;
|
||||||
}
|
handleAddUser();
|
||||||
}}
|
}
|
||||||
placeholder="user@example.com"
|
}}
|
||||||
className="flex-1"
|
placeholder="user@example.com"
|
||||||
/>
|
className="flex-1"
|
||||||
<Button onClick={handleAddUser} variant="secondary" disabled={isUpdating}>
|
/>
|
||||||
{__("Add")}
|
<Button onClick={handleAddUser} variant="secondary" disabled={isUpdating}>
|
||||||
</Button>
|
{__("Add")}
|
||||||
</div>
|
</Button>
|
||||||
|
|
||||||
{currentExcludedUserNames.length > 0 && (
|
|
||||||
<div className="space-y-2">
|
|
||||||
{currentExcludedUserNames.map((user: string) => (
|
|
||||||
<div
|
|
||||||
key={user}
|
|
||||||
className="flex items-center justify-between p-2 bg-subtle rounded"
|
|
||||||
>
|
|
||||||
<span className="text-sm">{user}</span>
|
|
||||||
<Button
|
|
||||||
variant="quaternary"
|
|
||||||
onClick={() => handleRemoveUser(user)}
|
|
||||||
disabled={isUpdating}
|
|
||||||
>
|
|
||||||
{__("Remove")}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
|
||||||
{currentExcludedUserNames.length === 0 && (
|
{currentExcludedUserNames.length > 0 && (
|
||||||
<p className="text-sm text-txt-secondary text-center py-4">
|
<div className="space-y-2">
|
||||||
{__("No excluded user names. All Google Workspace / Cloud Identity users will be synced.")}
|
{currentExcludedUserNames.map((user: string) => (
|
||||||
</p>
|
<div
|
||||||
)}
|
key={user}
|
||||||
|
className="flex items-center justify-between p-2 bg-subtle rounded"
|
||||||
|
>
|
||||||
|
<span className="text-sm">{user}</span>
|
||||||
|
<Button
|
||||||
|
variant="quaternary"
|
||||||
|
onClick={() => handleRemoveUser(user)}
|
||||||
|
disabled={isUpdating}
|
||||||
|
>
|
||||||
|
{__("Remove")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{currentExcludedUserNames.length === 0 && (
|
||||||
|
<p className="text-sm text-txt-secondary text-center py-4">
|
||||||
|
{__("No excluded user names. All Google Workspace / Cloud Identity users will be synced.")}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
<Dialog
|
||||||
|
ref={dialogRef}
|
||||||
|
trigger={(
|
||||||
|
<Button variant="danger">
|
||||||
|
{__("Disconnect")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
title={__("Disconnect Google Workspace / Cloud Identity")}
|
||||||
|
className="max-w-lg"
|
||||||
|
>
|
||||||
|
<DialogContent padded className="space-y-4">
|
||||||
|
<p className="text-txt-secondary text-sm">
|
||||||
|
{__(
|
||||||
|
"This will disconnect your Google Workspace / Cloud Identity integration. Users will no longer be automatically synced via SCIM.",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<p className="text-red-600 text-sm font-medium">
|
||||||
|
{__("This action cannot be undone.")}
|
||||||
|
</p>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button
|
||||||
|
variant="danger"
|
||||||
|
onClick={handleDisconnect}
|
||||||
|
disabled={isDeleting}
|
||||||
|
>
|
||||||
|
{isDeleting
|
||||||
|
? __("Disconnecting...")
|
||||||
|
: __("Disconnect")}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isBridgeFailed && (
|
||||||
|
<div className="flex items-start gap-2 rounded-lg bg-bg-warning/10 border border-border-warning p-3">
|
||||||
|
<IconWarning size={16} className="text-txt-danger shrink-0 mt-0.5" />
|
||||||
|
<div className="space-y-1">
|
||||||
|
<p className="text-sm font-medium text-txt-danger">
|
||||||
|
{__("Bridge is in an error state")}
|
||||||
|
</p>
|
||||||
|
<p className="text-sm text-txt-secondary whitespace-pre-wrap break-all">
|
||||||
|
{bridgeErrorMessage}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</DialogContent>
|
</div>
|
||||||
</Dialog>
|
)}
|
||||||
<Dialog
|
|
||||||
ref={dialogRef}
|
|
||||||
trigger={(
|
|
||||||
<Button variant="danger">
|
|
||||||
{__("Disconnect")}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
title={__("Disconnect Google Workspace / Cloud Identity")}
|
|
||||||
className="max-w-lg"
|
|
||||||
>
|
|
||||||
<DialogContent padded className="space-y-4">
|
|
||||||
<p className="text-txt-secondary text-sm">
|
|
||||||
{__(
|
|
||||||
"This will disconnect your Google Workspace / Cloud Identity integration. Users will no longer be automatically synced via SCIM.",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<p className="text-red-600 text-sm font-medium">
|
|
||||||
{__("This action cannot be undone.")}
|
|
||||||
</p>
|
|
||||||
</DialogContent>
|
|
||||||
<DialogFooter>
|
|
||||||
<Button
|
|
||||||
variant="danger"
|
|
||||||
onClick={handleDisconnect}
|
|
||||||
disabled={isDeleting}
|
|
||||||
>
|
|
||||||
{isDeleting
|
|
||||||
? __("Disconnecting...")
|
|
||||||
: __("Disconnect")}
|
|
||||||
</Button>
|
|
||||||
</DialogFooter>
|
|
||||||
</Dialog>
|
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
DialogContent,
|
DialogContent,
|
||||||
DialogFooter,
|
DialogFooter,
|
||||||
IconSettingsGear2,
|
IconSettingsGear2,
|
||||||
|
IconWarning,
|
||||||
Input,
|
Input,
|
||||||
Microsoft,
|
Microsoft,
|
||||||
useDialogRef,
|
useDialogRef,
|
||||||
@@ -38,9 +39,18 @@ import { useOrganizationId } from "#/hooks/useOrganizationId";
|
|||||||
const microsoft365ConnectorFragment = graphql`
|
const microsoft365ConnectorFragment = graphql`
|
||||||
fragment Microsoft365ConnectorFragment on SCIMConfiguration {
|
fragment Microsoft365ConnectorFragment on SCIMConfiguration {
|
||||||
id
|
id
|
||||||
|
events(first: 1) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
errorMessage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
bridge {
|
bridge {
|
||||||
id
|
id
|
||||||
type
|
type
|
||||||
|
state
|
||||||
excludedUserNames
|
excludedUserNames
|
||||||
connector {
|
connector {
|
||||||
id
|
id
|
||||||
@@ -83,9 +93,26 @@ export function Microsoft365Connector(props: {
|
|||||||
const connector = bridge?.connector;
|
const connector = bridge?.connector;
|
||||||
const scimConfigurationId = data?.id;
|
const scimConfigurationId = data?.id;
|
||||||
const bridgeId = bridge?.id;
|
const bridgeId = bridge?.id;
|
||||||
|
|
||||||
const organizationId = useOrganizationId();
|
const organizationId = useOrganizationId();
|
||||||
const { __, dateTimeFormat } = useTranslate();
|
const { __, dateTimeFormat } = useTranslate();
|
||||||
|
const bridgeState = bridge?.state ?? null;
|
||||||
|
const latestBridgeError = data?.events?.edges?.[0]?.node?.errorMessage ?? null;
|
||||||
|
const isBridgeFailed = bridgeState === "FAILED";
|
||||||
|
const isBridgePending = bridgeState === "PENDING";
|
||||||
|
const bridgeStatusBadgeVariant = isBridgeFailed
|
||||||
|
? "danger"
|
||||||
|
: isBridgePending
|
||||||
|
? "warning"
|
||||||
|
: "success";
|
||||||
|
const bridgeStatusLabel = isBridgeFailed
|
||||||
|
? __("Error")
|
||||||
|
: isBridgePending
|
||||||
|
? __("Syncing")
|
||||||
|
: __("Connected");
|
||||||
|
const bridgeErrorMessage = latestBridgeError
|
||||||
|
?? __(
|
||||||
|
"The bridge is currently failing to sync. Check the provisioning event history for more details.",
|
||||||
|
);
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const dialogRef = useDialogRef();
|
const dialogRef = useDialogRef();
|
||||||
const excludedUserNamesDialogRef = useDialogRef();
|
const excludedUserNamesDialogRef = useDialogRef();
|
||||||
@@ -223,118 +250,134 @@ export function Microsoft365Connector(props: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card padded className="flex items-center gap-3">
|
<Card padded className="space-y-3">
|
||||||
<div className="w-10 h-10 flex items-center justify-center bg-subtle rounded">
|
<div className="flex items-center gap-3">
|
||||||
<Microsoft className="w-6 h-6" />
|
<div className="w-10 h-10 flex items-center justify-center bg-subtle rounded">
|
||||||
</div>
|
<Microsoft className="w-6 h-6" />
|
||||||
<div className="mr-auto">
|
</div>
|
||||||
<h3 className="font-medium">{__("Microsoft 365")}</h3>
|
<div className="mr-auto">
|
||||||
<p className="text-sm text-txt-secondary">
|
<h3 className="font-medium">{__("Microsoft 365")}</h3>
|
||||||
{sprintf(__("Connected on %s"), dateTimeFormat(connector.createdAt))}
|
<p className="text-sm text-txt-secondary">
|
||||||
</p>
|
{sprintf(__("Connected on %s"), dateTimeFormat(connector.createdAt))}
|
||||||
</div>
|
</p>
|
||||||
<Badge variant="success" size="md">
|
</div>
|
||||||
{__("Connected")}
|
<Badge variant={bridgeStatusBadgeVariant} size="md">
|
||||||
</Badge>
|
{bridgeStatusLabel}
|
||||||
<Dialog
|
</Badge>
|
||||||
ref={excludedUserNamesDialogRef}
|
<Dialog
|
||||||
trigger={(
|
ref={excludedUserNamesDialogRef}
|
||||||
<Button variant="secondary">
|
trigger={(
|
||||||
<IconSettingsGear2 size={16} />
|
<Button variant="secondary">
|
||||||
{__("Settings")}
|
<IconSettingsGear2 size={16} />
|
||||||
</Button>
|
{__("Settings")}
|
||||||
)}
|
</Button>
|
||||||
title={__("Microsoft 365 Settings")}
|
)}
|
||||||
className="max-w-lg"
|
title={__("Microsoft 365 Settings")}
|
||||||
>
|
className="max-w-lg"
|
||||||
<DialogContent padded className="space-y-6">
|
>
|
||||||
<div className="space-y-4">
|
<DialogContent padded className="space-y-6">
|
||||||
<div>
|
<div className="space-y-4">
|
||||||
<h4 className="text-sm font-medium">{__("Excluded user names")}</h4>
|
<div>
|
||||||
<p className="text-sm text-txt-secondary mt-1">
|
<h4 className="text-sm font-medium">{__("Excluded user names")}</h4>
|
||||||
{__("Users with these user names will not be synced from Microsoft 365.")}
|
<p className="text-sm text-txt-secondary mt-1">
|
||||||
</p>
|
{__("Users with these user names will not be synced from Microsoft 365.")}
|
||||||
</div>
|
</p>
|
||||||
<div className="flex gap-2">
|
</div>
|
||||||
<Input
|
<div className="flex gap-2">
|
||||||
type="text"
|
<Input
|
||||||
value={newUser}
|
type="text"
|
||||||
onChange={e => setNewUser(e.target.value)}
|
value={newUser}
|
||||||
onKeyDown={(e) => {
|
onChange={e => setNewUser(e.target.value)}
|
||||||
if (e.key === "Enter") {
|
onKeyDown={(e) => {
|
||||||
e.preventDefault();
|
if (e.key === "Enter") {
|
||||||
if (isUpdating) return;
|
e.preventDefault();
|
||||||
handleAddUser();
|
if (isUpdating) return;
|
||||||
}
|
handleAddUser();
|
||||||
}}
|
}
|
||||||
placeholder="user@example.com"
|
}}
|
||||||
className="flex-1"
|
placeholder="user@example.com"
|
||||||
/>
|
className="flex-1"
|
||||||
<Button onClick={handleAddUser} variant="secondary" disabled={isUpdating}>
|
/>
|
||||||
{__("Add")}
|
<Button onClick={handleAddUser} variant="secondary" disabled={isUpdating}>
|
||||||
</Button>
|
{__("Add")}
|
||||||
</div>
|
</Button>
|
||||||
|
|
||||||
{currentExcludedUserNames.length > 0 && (
|
|
||||||
<div className="space-y-2">
|
|
||||||
{currentExcludedUserNames.map((user: string) => (
|
|
||||||
<div
|
|
||||||
key={user}
|
|
||||||
className="flex items-center justify-between p-2 bg-subtle rounded"
|
|
||||||
>
|
|
||||||
<span className="text-sm">{user}</span>
|
|
||||||
<Button
|
|
||||||
variant="quaternary"
|
|
||||||
onClick={() => handleRemoveUser(user)}
|
|
||||||
disabled={isUpdating}
|
|
||||||
>
|
|
||||||
{__("Remove")}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
|
||||||
{currentExcludedUserNames.length === 0 && (
|
{currentExcludedUserNames.length > 0 && (
|
||||||
<p className="text-sm text-txt-secondary text-center py-4">
|
<div className="space-y-2">
|
||||||
{__("No excluded user names. All Microsoft 365 users will be synced.")}
|
{currentExcludedUserNames.map((user: string) => (
|
||||||
</p>
|
<div
|
||||||
)}
|
key={user}
|
||||||
|
className="flex items-center justify-between p-2 bg-subtle rounded"
|
||||||
|
>
|
||||||
|
<span className="text-sm">{user}</span>
|
||||||
|
<Button
|
||||||
|
variant="quaternary"
|
||||||
|
onClick={() => handleRemoveUser(user)}
|
||||||
|
disabled={isUpdating}
|
||||||
|
>
|
||||||
|
{__("Remove")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{currentExcludedUserNames.length === 0 && (
|
||||||
|
<p className="text-sm text-txt-secondary text-center py-4">
|
||||||
|
{__("No excluded user names. All Microsoft 365 users will be synced.")}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
<Dialog
|
||||||
|
ref={dialogRef}
|
||||||
|
trigger={(
|
||||||
|
<Button variant="danger">
|
||||||
|
{__("Disconnect")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
title={__("Disconnect Microsoft 365")}
|
||||||
|
className="max-w-lg"
|
||||||
|
>
|
||||||
|
<DialogContent padded className="space-y-4">
|
||||||
|
<p className="text-txt-secondary text-sm">
|
||||||
|
{__(
|
||||||
|
"This will disconnect your Microsoft 365 integration. Users will no longer be automatically synced via SCIM.",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<p className="text-red-600 text-sm font-medium">
|
||||||
|
{__("This action cannot be undone.")}
|
||||||
|
</p>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button
|
||||||
|
variant="danger"
|
||||||
|
onClick={handleDisconnect}
|
||||||
|
disabled={isDeleting}
|
||||||
|
>
|
||||||
|
{isDeleting
|
||||||
|
? __("Disconnecting...")
|
||||||
|
: __("Disconnect")}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isBridgeFailed && (
|
||||||
|
<div className="flex items-start gap-2 rounded-lg bg-bg-warning/10 border border-border-warning p-3">
|
||||||
|
<IconWarning size={16} className="text-txt-danger shrink-0 mt-0.5" />
|
||||||
|
<div className="space-y-1">
|
||||||
|
<p className="text-sm font-medium text-txt-danger">
|
||||||
|
{__("Bridge is in an error state")}
|
||||||
|
</p>
|
||||||
|
<p className="text-sm text-txt-secondary whitespace-pre-wrap break-all">
|
||||||
|
{bridgeErrorMessage}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</DialogContent>
|
</div>
|
||||||
</Dialog>
|
)}
|
||||||
<Dialog
|
|
||||||
ref={dialogRef}
|
|
||||||
trigger={(
|
|
||||||
<Button variant="danger">
|
|
||||||
{__("Disconnect")}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
title={__("Disconnect Microsoft 365")}
|
|
||||||
className="max-w-lg"
|
|
||||||
>
|
|
||||||
<DialogContent padded className="space-y-4">
|
|
||||||
<p className="text-txt-secondary text-sm">
|
|
||||||
{__(
|
|
||||||
"This will disconnect your Microsoft 365 integration. Users will no longer be automatically synced via SCIM.",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<p className="text-red-600 text-sm font-medium">
|
|
||||||
{__("This action cannot be undone.")}
|
|
||||||
</p>
|
|
||||||
</DialogContent>
|
|
||||||
<DialogFooter>
|
|
||||||
<Button
|
|
||||||
variant="danger"
|
|
||||||
onClick={handleDisconnect}
|
|
||||||
disabled={isDeleting}
|
|
||||||
>
|
|
||||||
{isDeleting
|
|
||||||
? __("Disconnecting...")
|
|
||||||
: __("Disconnect")}
|
|
||||||
</Button>
|
|
||||||
</DialogFooter>
|
|
||||||
</Dialog>
|
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user