@red-hat-developer-hub/backstage-plugin-intelligent-assistant 3.0.2 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/alpha.d.ts +15 -1
  3. package/dist/components/LightSpeedChat.esm.js +1 -1
  4. package/dist/components/LightspeedChatContainer.esm.js +4 -2
  5. package/dist/components/LightspeedChatContainer.esm.js.map +1 -1
  6. package/dist/components/McpConfigureServerModal.esm.js +407 -0
  7. package/dist/components/McpConfigureServerModal.esm.js.map +1 -0
  8. package/dist/components/McpServersSettings.esm.js +92 -475
  9. package/dist/components/McpServersSettings.esm.js.map +1 -1
  10. package/dist/components/mcpServersDisplayUtils.esm.js +167 -0
  11. package/dist/components/mcpServersDisplayUtils.esm.js.map +1 -0
  12. package/dist/hooks/useMcpConfigureModal.esm.js +465 -0
  13. package/dist/hooks/useMcpConfigureModal.esm.js.map +1 -0
  14. package/dist/index.esm.js +5 -4
  15. package/dist/index.esm.js.map +1 -1
  16. package/dist/legacy.esm.js +0 -2
  17. package/dist/legacy.esm.js.map +1 -1
  18. package/dist/plugin.esm.js +0 -2
  19. package/dist/plugin.esm.js.map +1 -1
  20. package/dist/translations/de.esm.js +16 -2
  21. package/dist/translations/de.esm.js.map +1 -1
  22. package/dist/translations/es.esm.js +16 -2
  23. package/dist/translations/es.esm.js.map +1 -1
  24. package/dist/translations/fr.esm.js +16 -2
  25. package/dist/translations/fr.esm.js.map +1 -1
  26. package/dist/translations/it.esm.js +16 -2
  27. package/dist/translations/it.esm.js.map +1 -1
  28. package/dist/translations/ja.esm.js +16 -2
  29. package/dist/translations/ja.esm.js.map +1 -1
  30. package/dist/translations/ref.esm.js +17 -3
  31. package/dist/translations/ref.esm.js.map +1 -1
  32. package/package.json +3 -6
@@ -1,21 +1,18 @@
1
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
- import { useState, useCallback, useEffect, useMemo } from 'react';
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { useState, useCallback, useEffect } from 'react';
3
3
  import { useApi, configApiRef, fetchApiRef } from '@backstage/core-plugin-api';
4
4
  import { usePermission } from '@backstage/plugin-permission-react';
5
5
  import { makeStyles } from '@material-ui/core';
6
- import CancelOutlinedIcon from '@mui/icons-material/CancelOutlined';
7
- import CloseOutlinedIcon from '@mui/icons-material/CloseOutlined';
8
- import IconButton from '@mui/material/IconButton';
9
- import InputAdornment from '@mui/material/InputAdornment';
10
- import TextField from '@mui/material/TextField';
11
6
  import Typography from '@mui/material/Typography';
12
- import { Title, Button, Alert, Switch, Tooltip, Modal } from '@patternfly/react-core';
13
- import { TimesIcon, SortAmountDownIcon, SortAmountUpIcon, PencilAltIcon, LockIcon, KeyIcon, InfoCircleIcon, ExclamationCircleIcon, CheckCircleIcon } from '@patternfly/react-icons';
7
+ import { Title, Button, Alert, Switch, Tooltip } from '@patternfly/react-core';
8
+ import { TimesIcon, PencilAltIcon, SortAmountDownIcon, KeyIcon, InfoCircleIcon, ExclamationCircleIcon, CheckCircleIcon, SortAmountUpIcon } from '@patternfly/react-icons';
14
9
  import { Table, Thead, Tr, Th, Tbody, Td } from '@patternfly/react-table';
15
10
  import { lightspeedMcpManagePermission } from '@red-hat-developer-hub/backstage-plugin-intelligent-assistant-common';
11
+ import { useMcpConfigureModal } from '../hooks/useMcpConfigureModal.esm.js';
16
12
  import { useTranslation } from '../hooks/useTranslation.esm.js';
13
+ import { McpConfigureServerModal } from './McpConfigureServerModal.esm.js';
14
+ import { formatApiError, getDisplayStatus, compareMcpServers, getDisplayDetail, getEnabledToggleChecked, isEnabledToggleUnavailable } from './mcpServersDisplayUtils.esm.js';
17
15
 
18
- const SAVED_TOKEN_MASK = "********************";
19
16
  const useStyles = makeStyles((theme) => ({
20
17
  "@global": {
21
18
  ".pf-v6-c-backdrop": {
@@ -68,6 +65,25 @@ const useStyles = makeStyles((theme) => ({
68
65
  display: "inline-flex",
69
66
  alignItems: "center"
70
67
  },
68
+ statusHeaderButton: {
69
+ paddingLeft: 0,
70
+ paddingTop: 0,
71
+ paddingBottom: 0,
72
+ fontWeight: 600,
73
+ fontSize: "0.75rem",
74
+ lineHeight: "1.25rem",
75
+ minHeight: "auto",
76
+ color: theme.palette.text.primary,
77
+ textDecoration: "none !important",
78
+ display: "inline-flex",
79
+ alignItems: "center"
80
+ },
81
+ sortHeaderIconActive: {
82
+ color: "var(--pf-t--global--icon--color--brand--default)"
83
+ },
84
+ sortHeaderIconInactive: {
85
+ color: "var(--pf-t--global--icon--color--subtle)"
86
+ },
71
87
  nameHeaderText: {
72
88
  paddingLeft: "7px",
73
89
  fontSize: "0.75rem",
@@ -102,13 +118,13 @@ const useStyles = makeStyles((theme) => ({
102
118
  fontSize: "0.875rem"
103
119
  },
104
120
  statusOk: {
105
- color: "#147878"
121
+ color: "var(--pf-t--global--icon--color--status--custom--default)"
106
122
  },
107
123
  statusWarn: {
108
- color: "#B1380B"
124
+ color: "var(--pf-t--global--icon--color--status--danger--default)"
109
125
  },
110
126
  statusDisabled: {
111
- color: theme.palette.type === "dark" ? "var(--pf-t--global--text--color--subtle, #c7c7c7)" : "var(--pf-t--global--text--color--subtle, #4d4d4d)"
127
+ color: "var(--pf-t--global--icon--color--subtle)"
112
128
  },
113
129
  actionButton: {
114
130
  color: theme.palette.text.secondary,
@@ -120,142 +136,6 @@ const useStyles = makeStyles((theme) => ({
120
136
  opacity: 1
121
137
  }
122
138
  },
123
- modalDescription: {
124
- color: theme.palette.text.secondary,
125
- fontSize: "0.875rem",
126
- marginTop: theme.spacing(2),
127
- marginBottom: theme.spacing(2)
128
- },
129
- modalContent: {
130
- position: "relative",
131
- padding: theme.spacing(3, 0, 3, 3),
132
- marginRight: theme.spacing(3)
133
- },
134
- modalCustomCloseButton: {
135
- position: "absolute",
136
- top: theme.spacing(2),
137
- right: theme.spacing(-0.5),
138
- color: theme.palette.text.primary
139
- },
140
- modalHeading: {
141
- display: "flex",
142
- alignItems: "center",
143
- gap: theme.spacing(0.75),
144
- marginBottom: theme.spacing(1),
145
- fontSize: "1.25rem",
146
- lineHeight: 1.4,
147
- fontWeight: 500,
148
- "& .v5-MuiTypography-root": {
149
- fontSize: "1.25rem",
150
- lineHeight: 1.4,
151
- fontWeight: 500
152
- }
153
- },
154
- tokenRow: {
155
- position: "relative"
156
- },
157
- tokenClearButton: {
158
- position: "absolute",
159
- right: theme.spacing(1),
160
- top: "50%",
161
- transform: "translateY(-50%)",
162
- zIndex: 1,
163
- color: theme.palette.action.active
164
- },
165
- tokenHelper: {
166
- color: theme.palette.text.secondary,
167
- fontSize: "0.75rem",
168
- marginTop: theme.spacing(0.5)
169
- },
170
- tokenInput: {
171
- marginTop: "1rem !important",
172
- "& .MuiOutlinedInput-root": {
173
- height: "3.5rem"
174
- },
175
- "& .MuiOutlinedInput-input": {
176
- padding: "0 0.875rem"
177
- },
178
- "& .MuiInputLabel-root": {
179
- fontSize: "0.875rem"
180
- }
181
- },
182
- tokenInputSuccess: {
183
- "& .MuiOutlinedInput-root .MuiOutlinedInput-notchedOutline": {
184
- borderColor: "#3E8635",
185
- borderWidth: 1
186
- },
187
- "& .MuiOutlinedInput-root:hover .MuiOutlinedInput-notchedOutline": {
188
- borderColor: "#3E8635"
189
- },
190
- "& .MuiOutlinedInput-root.Mui-focused .MuiOutlinedInput-notchedOutline": {
191
- borderColor: "#3E8635"
192
- },
193
- "& .MuiInputLabel-root.Mui-focused": {
194
- color: "#3E8635"
195
- }
196
- },
197
- tokenInputError: {
198
- "& .MuiOutlinedInput-root .MuiOutlinedInput-notchedOutline": {
199
- borderColor: "#C9190B",
200
- borderWidth: 1
201
- },
202
- "& .MuiOutlinedInput-root:hover .MuiOutlinedInput-notchedOutline": {
203
- borderColor: "#C9190B"
204
- },
205
- "& .MuiOutlinedInput-root.Mui-focused .MuiOutlinedInput-notchedOutline": {
206
- borderColor: "#C9190B"
207
- },
208
- "& .MuiInputLabel-root.Mui-focused": {
209
- color: "#C9190B"
210
- }
211
- },
212
- modalActions: {
213
- marginTop: theme.spacing(3),
214
- display: "flex",
215
- gap: theme.spacing(1)
216
- },
217
- modalActionButton: {
218
- fontSize: "1rem"
219
- },
220
- modalCancelButton: {
221
- fontSize: "1rem"
222
- },
223
- forgetTokenButton: {
224
- fontSize: "1rem",
225
- border: "1px solid #B1380B",
226
- borderRadius: "1.25rem",
227
- padding: "0.375rem 1rem",
228
- color: "#B1380B",
229
- backgroundColor: "transparent",
230
- marginLeft: theme.spacing(1),
231
- marginRight: theme.spacing(1),
232
- boxShadow: "none",
233
- "&:hover": {
234
- backgroundColor: "rgba(201, 25, 11, 0.08)"
235
- }
236
- },
237
- configureModal: {
238
- "& .pf-v6-c-modal-box": {
239
- width: "608px",
240
- maxWidth: "608px",
241
- height: "326px",
242
- minHeight: "326px"
243
- },
244
- "& .pf-v6-c-modal-box__title, & .pf-v6-c-modal-box__title-text, & .pf-v5-c-modal-box__title, & .pf-v5-c-modal-box__title-text": {
245
- fontSize: "1.25rem !important",
246
- lineHeight: "1.4 !important"
247
- },
248
- "& .pf-v6-c-modal-box__close": {
249
- display: "none"
250
- },
251
- "& .pf-v5-c-modal-box__close": {
252
- display: "none"
253
- },
254
- "& .pf-v6-c-button__icon": {
255
- paddingTop: "5px !important",
256
- fontSize: "1.25rem !important"
257
- }
258
- },
259
139
  toggleCell: {
260
140
  paddingRight: "0 !important"
261
141
  },
@@ -295,13 +175,6 @@ const getStatusIcon = (status, className) => {
295
175
  return /* @__PURE__ */ jsx(ExclamationCircleIcon, { className });
296
176
  return /* @__PURE__ */ jsx(CheckCircleIcon, { className });
297
177
  };
298
- const getDisplayStatus = (server) => {
299
- if (!server.hasToken) return "tokenRequired";
300
- if (!server.enabled) return "disabled";
301
- if (server.status === "error") return "failed";
302
- if (server.status === "connected") return "ok";
303
- return "unknown";
304
- };
305
178
  const toUiServer = (server, validationError) => ({
306
179
  id: server.name,
307
180
  name: server.name,
@@ -311,6 +184,7 @@ const toUiServer = (server, validationError) => ({
311
184
  toolCount: server.toolCount,
312
185
  hasToken: server.hasToken,
313
186
  hasUserToken: server.hasUserToken,
187
+ hasOrgToken: server.hasOrgToken,
314
188
  validationError: server.status === "error" ? validationError : void 0,
315
189
  auth: server.auth
316
190
  });
@@ -327,16 +201,11 @@ const McpServersSettings = ({
327
201
  });
328
202
  const canManageMcp = mcpManagePermission.allowed;
329
203
  const [servers, setServers] = useState([]);
204
+ const [sortColumn, setSortColumn] = useState("name");
330
205
  const [sortAsc, setSortAsc] = useState(true);
331
206
  const [isLoading, setIsLoading] = useState(true);
332
207
  const [isSaving, setIsSaving] = useState({});
333
208
  const [error, setError] = useState(null);
334
- const [editingServerId, setEditingServerId] = useState(null);
335
- const [tokenInputValue, setTokenInputValue] = useState("");
336
- const [hasSavedTokenInModal, setHasSavedTokenInModal] = useState(false);
337
- const [canRemovePersonalToken, setCanRemovePersonalToken] = useState(false);
338
- const [tokenValidationState, setTokenValidationState] = useState("idle");
339
- const [tokenValidationMessage, setTokenValidationMessage] = useState("");
340
209
  const getBaseUrl = useCallback(() => {
341
210
  return `${configApi.getString("backend.baseUrl")}/api/intelligent-assistant`;
342
211
  }, [configApi]);
@@ -367,28 +236,34 @@ const McpServersSettings = ({
367
236
  },
368
237
  [fetchApi]
369
238
  );
370
- const validateServer = useCallback(
239
+ const fetchServerValidation = useCallback(
371
240
  async (serverName) => {
372
241
  const baseUrl = getBaseUrl();
373
- const data = await fetchJson(
242
+ return fetchJson(
374
243
  `${baseUrl}/mcp-servers/${encodeURIComponent(serverName)}/validate`,
375
244
  {
376
245
  method: "POST"
377
246
  }
378
247
  );
248
+ },
249
+ [fetchJson, getBaseUrl]
250
+ );
251
+ const validateServer = useCallback(
252
+ async (serverName) => {
253
+ const data = await fetchServerValidation(serverName);
379
254
  setServers(
380
255
  (prev) => prev.map(
381
256
  (server) => server.name === serverName ? {
382
257
  ...server,
383
258
  status: data.status,
384
259
  toolCount: data.toolCount,
385
- validationError: data.status === "error" ? data.validation?.error ?? "Validation failed" : void 0
260
+ validationError: data.status === "error" ? formatApiError(data.validation?.error) || "Validation failed" : void 0
386
261
  } : server
387
262
  )
388
263
  );
389
264
  return data;
390
265
  },
391
- [fetchJson, getBaseUrl]
266
+ [fetchServerValidation]
392
267
  );
393
268
  const validateCredentials = useCallback(
394
269
  async (url, token) => {
@@ -474,210 +349,43 @@ const McpServersSettings = ({
474
349
  },
475
350
  [canManageMcp, fetchJson, getBaseUrl, loadServers]
476
351
  );
477
- const editingServer = useMemo(
478
- () => servers.find((server) => server.id === editingServerId),
479
- [servers, editingServerId]
480
- );
481
- const selectedCount = useMemo(
482
- () => servers.filter((server) => {
483
- const displayStatus = getDisplayStatus(server);
484
- const isUnavailable = displayStatus === "failed" || displayStatus === "tokenRequired";
485
- return server.enabled && !isUnavailable;
486
- }).length,
487
- [servers]
488
- );
489
- const sortedServers = useMemo(() => {
490
- const next = [...servers];
491
- next.sort(
492
- (a, b) => sortAsc ? a.name.localeCompare(b.name) : b.name.localeCompare(a.name)
493
- );
494
- return next;
495
- }, [servers, sortAsc]);
496
- const getDisplayDetail = useCallback(
497
- (server, displayStatus) => {
498
- if (displayStatus === "disabled")
499
- return t("mcp.settings.status.disabled");
500
- if (displayStatus === "tokenRequired")
501
- return t("mcp.settings.status.tokenRequired");
502
- if (displayStatus === "failed") return t("mcp.settings.status.failed");
503
- if (displayStatus === "ok") {
504
- if (server.toolCount === 1) {
505
- return t("mcp.settings.status.oneTool", {
506
- count: String(server.toolCount)
507
- });
508
- }
509
- return t("mcp.settings.status.manyTools", {
510
- count: String(server.toolCount)
511
- });
512
- }
513
- return t("mcp.settings.status.unknown");
514
- },
515
- [t]
352
+ const configureModal = useMcpConfigureModal({
353
+ servers,
354
+ canManageMcp,
355
+ isSaving,
356
+ patchServer,
357
+ validateServer,
358
+ validateCredentials,
359
+ fetchServerValidation
360
+ });
361
+ const selectedCount = servers.filter((server) => {
362
+ const displayStatus = getDisplayStatus(server);
363
+ const isUnavailable = displayStatus === "failed" || displayStatus === "tokenRequired";
364
+ return server.enabled && !isUnavailable;
365
+ }).length;
366
+ const sortedServers = [...servers].sort(
367
+ (a, b) => compareMcpServers(a, b, sortColumn, sortAsc)
516
368
  );
517
- const closeConfigureModal = useCallback(() => {
518
- setEditingServerId(null);
519
- setTokenInputValue("");
520
- setHasSavedTokenInModal(false);
521
- setCanRemovePersonalToken(false);
522
- setTokenValidationState("idle");
523
- setTokenValidationMessage("");
524
- }, []);
525
- const openConfigureModal = (server) => {
526
- setEditingServerId(server.id);
527
- const hasSavedToken = server.hasToken;
528
- setHasSavedTokenInModal(hasSavedToken);
529
- setCanRemovePersonalToken(server.hasUserToken);
530
- setTokenInputValue(hasSavedToken ? SAVED_TOKEN_MASK : "");
531
- if (server.status === "error" && server.validationError) {
532
- setTokenValidationState("error");
533
- setTokenValidationMessage(server.validationError);
534
- } else {
535
- setTokenValidationState("idle");
536
- setTokenValidationMessage("");
537
- }
538
- };
539
- const onTokenInputChange = (value) => {
540
- if (hasSavedTokenInModal && value !== SAVED_TOKEN_MASK) {
541
- setHasSavedTokenInModal(false);
542
- }
543
- setTokenInputValue(value);
544
- setTokenValidationState("idle");
545
- setTokenValidationMessage("");
546
- };
547
- const clearTokenInput = () => {
548
- if (hasSavedTokenInModal) {
549
- setHasSavedTokenInModal(false);
369
+ const onSortColumnClick = (column) => {
370
+ if (sortColumn === column) {
371
+ setSortAsc((prev) => !prev);
372
+ return;
550
373
  }
551
- setTokenInputValue("");
552
- setTokenValidationState("idle");
553
- setTokenValidationMessage("");
374
+ setSortColumn(column);
375
+ setSortAsc(true);
554
376
  };
555
- let tokenInputStateClass = "";
556
- let tokenHelperColor;
557
- if (tokenValidationState === "success") {
558
- tokenInputStateClass = classes.tokenInputSuccess;
559
- tokenHelperColor = "#3E8635";
560
- } else if (tokenValidationState === "error") {
561
- tokenInputStateClass = classes.tokenInputError;
562
- tokenHelperColor = "#C9190B";
563
- }
564
- let tokenInputAdornment = /* @__PURE__ */ jsx(
565
- IconButton,
566
- {
567
- "aria-label": t("mcp.settings.token.clearAriaLabel"),
568
- size: "small",
569
- className: classes.tokenClearButton,
570
- onClick: clearTokenInput,
571
- children: /* @__PURE__ */ jsx(
572
- CancelOutlinedIcon,
573
- {
574
- style: {
575
- fontSize: 24,
576
- width: 24,
577
- height: 24
578
- }
579
- }
580
- )
377
+ const renderSortIcon = (column) => {
378
+ const isActive = sortColumn === column;
379
+ let Icon = SortAmountDownIcon;
380
+ if (isActive && !sortAsc) {
381
+ Icon = SortAmountUpIcon;
581
382
  }
582
- );
583
- if (tokenValidationState === "success") {
584
- tokenInputAdornment = /* @__PURE__ */ jsx(
585
- CheckCircleIcon,
586
- {
587
- style: {
588
- color: "#3E8635",
589
- fontSize: 20,
590
- width: 20,
591
- height: 20,
592
- marginRight: 3
593
- }
594
- }
595
- );
596
- } else if (tokenValidationState === "error") {
597
- tokenInputAdornment = /* @__PURE__ */ jsx(
598
- ExclamationCircleIcon,
383
+ return /* @__PURE__ */ jsx(
384
+ Icon,
599
385
  {
600
- style: {
601
- color: "#C9190B",
602
- fontSize: 20,
603
- width: 20,
604
- height: 20,
605
- marginRight: 3
606
- }
386
+ className: isActive ? classes.sortHeaderIconActive : classes.sortHeaderIconInactive
607
387
  }
608
388
  );
609
- }
610
- const isUsingOrganizationCredentialInModal = hasSavedTokenInModal && !canRemovePersonalToken;
611
- const saveServerToken = useCallback(async () => {
612
- if (!editingServer || !canManageMcp) return;
613
- if (hasSavedTokenInModal && tokenInputValue === SAVED_TOKEN_MASK) {
614
- closeConfigureModal();
615
- return;
616
- }
617
- const token = tokenInputValue.trim();
618
- const hasToken = token.length > 0;
619
- setTokenValidationState("validating");
620
- setTokenValidationMessage(t("mcp.settings.token.validating"));
621
- try {
622
- if (hasToken) {
623
- if (!editingServer.url) {
624
- setTokenValidationState("error");
625
- setTokenValidationMessage(
626
- t("mcp.settings.token.urlUnavailableForValidation")
627
- );
628
- return;
629
- }
630
- const credentialValidation = await validateCredentials(
631
- editingServer.url,
632
- token
633
- );
634
- if (!credentialValidation.valid) {
635
- setTokenValidationState("error");
636
- setTokenValidationMessage(
637
- credentialValidation.error ?? t("mcp.settings.token.invalidCredentials")
638
- );
639
- return;
640
- }
641
- }
642
- setTokenValidationMessage(t("mcp.settings.token.savingAndValidating"));
643
- await patchServer(editingServer.name, {
644
- enabled: editingServer.enabled,
645
- token: hasToken ? token : null
646
- });
647
- const validationResult = await validateServer(editingServer.name);
648
- if (validationResult.status === "error") {
649
- setTokenValidationState("error");
650
- setTokenValidationMessage(
651
- validationResult.validation?.error ?? t("mcp.settings.token.validationFailed")
652
- );
653
- return;
654
- }
655
- setTokenValidationState("success");
656
- setTokenValidationMessage(t("mcp.settings.token.connectionSuccessful"));
657
- closeConfigureModal();
658
- } catch (e) {
659
- setTokenValidationState("error");
660
- setTokenValidationMessage(
661
- e instanceof Error ? e.message : `Failed to update ${editingServer.name} token`
662
- );
663
- }
664
- }, [
665
- canManageMcp,
666
- closeConfigureModal,
667
- editingServer,
668
- hasSavedTokenInModal,
669
- patchServer,
670
- t,
671
- tokenInputValue,
672
- validateCredentials,
673
- validateServer
674
- ]);
675
- const removePersonalToken = () => {
676
- setHasSavedTokenInModal(false);
677
- setCanRemovePersonalToken(false);
678
- setTokenInputValue("");
679
- setTokenValidationState("idle");
680
- setTokenValidationMessage("");
681
389
  };
682
390
  return /* @__PURE__ */ jsxs(
683
391
  "div",
@@ -736,13 +444,23 @@ const McpServersSettings = ({
736
444
  {
737
445
  variant: "link",
738
446
  className: classes.nameHeaderButton,
739
- icon: sortAsc ? /* @__PURE__ */ jsx(SortAmountDownIcon, {}) : /* @__PURE__ */ jsx(SortAmountUpIcon, {}),
447
+ icon: renderSortIcon("name"),
740
448
  iconPosition: "right",
741
- onClick: () => setSortAsc((prev) => !prev),
449
+ onClick: () => onSortColumnClick("name"),
742
450
  children: /* @__PURE__ */ jsx(Typography, { component: "span", className: classes.nameHeaderText, children: t("mcp.settings.name") })
743
451
  }
744
452
  ) }),
745
- /* @__PURE__ */ jsx(Th, { className: classes.statusHeader, children: t("mcp.settings.status") }),
453
+ /* @__PURE__ */ jsx(Th, { className: classes.statusHeader, children: /* @__PURE__ */ jsx(
454
+ Button,
455
+ {
456
+ variant: "link",
457
+ className: classes.statusHeaderButton,
458
+ icon: renderSortIcon("status"),
459
+ iconPosition: "right",
460
+ onClick: () => onSortColumnClick("status"),
461
+ children: /* @__PURE__ */ jsx(Typography, { component: "span", className: classes.nameHeaderText, children: t("mcp.settings.status") })
462
+ }
463
+ ) }),
746
464
  /* @__PURE__ */ jsx(Th, { screenReaderText: t("mcp.settings.edit") })
747
465
  ] }) }),
748
466
  /* @__PURE__ */ jsxs(Tbody, { children: [
@@ -750,7 +468,7 @@ const McpServersSettings = ({
750
468
  !isLoading && sortedServers.length === 0 && /* @__PURE__ */ jsx(Tr, { children: /* @__PURE__ */ jsx(Td, { colSpan: 4, children: t("mcp.settings.noneAvailable") }) }),
751
469
  sortedServers.map((server) => {
752
470
  const displayStatus = getDisplayStatus(server);
753
- const displayDetail = getDisplayDetail(server, displayStatus);
471
+ const displayDetail = getDisplayDetail(server, displayStatus, t);
754
472
  let statusClass = classes.statusWarn;
755
473
  if (displayStatus === "ok") {
756
474
  statusClass = classes.statusOk;
@@ -759,8 +477,11 @@ const McpServersSettings = ({
759
477
  }
760
478
  return /* @__PURE__ */ jsxs(Tr, { className: classes.tableRow, children: [
761
479
  /* @__PURE__ */ jsx(Td, { width: 10, className: classes.toggleCell, children: (() => {
762
- const isUnavailable = displayStatus === "failed" || displayStatus === "tokenRequired";
763
- const isChecked = isUnavailable ? false : server.enabled;
480
+ const isUnavailable = isEnabledToggleUnavailable(displayStatus);
481
+ const isChecked = getEnabledToggleChecked(
482
+ server,
483
+ displayStatus
484
+ );
764
485
  const isRowSaving = Boolean(isSaving[server.name]);
765
486
  const isToggleDisabled = isUnavailable || isRowSaving || !canManageMcp;
766
487
  const switchControl = /* @__PURE__ */ jsx(
@@ -830,7 +551,7 @@ const McpServersSettings = ({
830
551
  variant: "plain",
831
552
  className: classes.actionButton,
832
553
  isDisabled: !canManageMcp,
833
- onClick: () => openConfigureModal(server)
554
+ onClick: () => configureModal.open(server)
834
555
  }
835
556
  ) })
836
557
  ] }, server.id);
@@ -839,111 +560,7 @@ const McpServersSettings = ({
839
560
  ]
840
561
  }
841
562
  ),
842
- /* @__PURE__ */ jsx(
843
- Modal,
844
- {
845
- variant: "small",
846
- title: t("mcp.settings.configureServerTitle", {
847
- serverName: editingServer?.name ?? ""
848
- }),
849
- isOpen: Boolean(editingServer),
850
- onClose: closeConfigureModal,
851
- className: classes.configureModal,
852
- children: /* @__PURE__ */ jsxs("div", { className: classes.modalContent, children: [
853
- /* @__PURE__ */ jsx(
854
- IconButton,
855
- {
856
- "aria-label": t("mcp.settings.closeConfigureModalAriaLabel"),
857
- size: "small",
858
- className: classes.modalCustomCloseButton,
859
- onClick: closeConfigureModal,
860
- children: /* @__PURE__ */ jsx(CloseOutlinedIcon, {})
861
- }
862
- ),
863
- /* @__PURE__ */ jsxs("div", { className: classes.modalHeading, children: [
864
- /* @__PURE__ */ jsx(LockIcon, {}),
865
- /* @__PURE__ */ jsx(Typography, { component: "div", children: t("mcp.settings.configureServerTitle", {
866
- serverName: editingServer?.name ?? ""
867
- }) })
868
- ] }),
869
- editingServer?.auth === "dcr" ? /* @__PURE__ */ jsxs(Fragment, { children: [
870
- /* @__PURE__ */ jsx("div", { className: classes.modalDescription, children: t("mcp.settings.modalDescriptionDcr") }),
871
- /* @__PURE__ */ jsx("div", { className: classes.modalActions, children: /* @__PURE__ */ jsx(
872
- Button,
873
- {
874
- variant: "primary",
875
- onClick: closeConfigureModal,
876
- className: classes.modalActionButton,
877
- children: t("common.cancel")
878
- },
879
- "close"
880
- ) })
881
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
882
- /* @__PURE__ */ jsx("div", { className: classes.modalDescription, children: t("mcp.settings.modalDescription") }),
883
- /* @__PURE__ */ jsxs("div", { className: classes.tokenRow, children: [
884
- /* @__PURE__ */ jsx(
885
- TextField,
886
- {
887
- id: "mcp-pat-input",
888
- type: "password",
889
- variant: "outlined",
890
- fullWidth: true,
891
- value: tokenInputValue,
892
- onChange: (event) => onTokenInputChange(event.target.value),
893
- className: `${classes.tokenInput} ${tokenInputStateClass}`,
894
- label: hasSavedTokenInModal ? t("mcp.settings.savedToken") : t("mcp.settings.personalAccessToken"),
895
- InputProps: {
896
- endAdornment: /* @__PURE__ */ jsx(InputAdornment, { position: "end", children: tokenInputAdornment })
897
- }
898
- }
899
- ),
900
- (isUsingOrganizationCredentialInModal || !hasSavedTokenInModal || tokenValidationState !== "idle") && /* @__PURE__ */ jsx(
901
- "div",
902
- {
903
- className: classes.tokenHelper,
904
- style: { color: tokenHelperColor },
905
- children: tokenValidationMessage || (isUsingOrganizationCredentialInModal ? t("mcp.settings.usingAdminCredential") : t("mcp.settings.enterToken"))
906
- }
907
- )
908
- ] }),
909
- /* @__PURE__ */ jsxs("div", { className: classes.modalActions, children: [
910
- /* @__PURE__ */ jsx(
911
- Button,
912
- {
913
- variant: "primary",
914
- onClick: () => void saveServerToken(),
915
- isDisabled: !canManageMcp || Boolean(isSaving[editingServer?.name ?? ""]) || tokenValidationState === "validating",
916
- className: classes.modalActionButton,
917
- children: t("modal.save")
918
- },
919
- "save"
920
- ),
921
- canRemovePersonalToken && hasSavedTokenInModal && /* @__PURE__ */ jsx(
922
- Button,
923
- {
924
- variant: "plain",
925
- onClick: removePersonalToken,
926
- isDisabled: !canManageMcp || Boolean(isSaving[editingServer?.name ?? ""]) || tokenValidationState === "validating",
927
- className: classes.forgetTokenButton,
928
- children: t("mcp.settings.removePersonalToken")
929
- },
930
- "forget-token"
931
- ),
932
- /* @__PURE__ */ jsx(
933
- Button,
934
- {
935
- variant: "link",
936
- onClick: closeConfigureModal,
937
- className: classes.modalCancelButton,
938
- children: t("common.cancel")
939
- },
940
- "cancel"
941
- )
942
- ] })
943
- ] })
944
- ] })
945
- }
946
- )
563
+ /* @__PURE__ */ jsx(McpConfigureServerModal, { ...configureModal })
947
564
  ]
948
565
  }
949
566
  );