@burdenoff/fluidgrids-fe-sdk 2026.1113.11 → 2026.1114.1

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 (64) hide show
  1. package/dist/{AiStudioPage-BP2cLXEK.js → AiStudioPage-C040XZyS.js} +4 -4
  2. package/dist/{DataSinkDashboardPage-CiFi9YXQ.js → DataSinkDashboardPage-BoyLrZnt.js} +1 -1
  3. package/dist/{ExecutionDetailPage-BfFrWnBj.js → ExecutionDetailPage-C0lNp122.js} +5 -5
  4. package/dist/{ExecutionsListPage-BRvj71Nd.js → ExecutionsListPage-BpHBy1JU.js} +4 -4
  5. package/dist/{FluidGridsProvider-Dxehlm-2.js → FluidGridsProvider-CPLfIarV.js} +2 -2
  6. package/dist/{HistoryPage-CjtgUOZr.js → HistoryPage-CCZS-hPw.js} +1 -1
  7. package/dist/{InsightsPage-BsGqBpUO.js → InsightsPage-C-Nm9YNI.js} +1 -1
  8. package/dist/{NodesPage-B8fqXtey.js → NodesPage-C3sh0aL9.js} +1 -1
  9. package/dist/{PaidTemplateConfirm-BUBCnbP0.js → PaidTemplateConfirm-D20JFNCb.js} +1 -1
  10. package/dist/{QuotaExhaustedDialog-BEsj0A-I.js → QuotaExhaustedDialog-DTpI0jfj.js} +1 -1
  11. package/dist/{SettingsPage-DpBhx95x.js → SettingsPage-CmIa107C.js} +828 -8
  12. package/dist/{TaskQueuesPage-DLzuumGp.js → TaskQueuesPage-BNoe05qF.js} +16 -2
  13. package/dist/{TemplateCard-DD3vJ3a5.js → TemplateCard-C_YMW80k.js} +44 -34
  14. package/dist/{TemplateConfigurationModal-BtzI-4fr.js → TemplateConfigurationModal-DmM7s1zk.js} +1 -1
  15. package/dist/{TemplateDetailPanel-BxFNnlXm.js → TemplateDetailPanel-k0esV8pr.js} +12 -15
  16. package/dist/{TemplatePreview-Ca7df91b.js → TemplatePreview-aKYt0Kad.js} +4 -4
  17. package/dist/{TemplatesPage-BSMLtMnf.js → TemplatesPage-DIVghCB6.js} +12 -10
  18. package/dist/{WorkflowBuilder-Bok_WtPQ.js → WorkflowBuilder-BsDvbIpS.js} +6 -6
  19. package/dist/{WorkflowBuilder-Dh8MYfZJ.js → WorkflowBuilder-knGFBAAk.js} +4 -4
  20. package/dist/{WorkflowSearchRedirect-bcWo68LY.js → WorkflowSearchRedirect-DFsQzQw9.js} +1 -1
  21. package/dist/{WorkflowsList-DURQu2ZD.js → WorkflowsList-DEN-0uQe.js} +6 -6
  22. package/dist/{agentTools-B4BDCe57.js → agentTools-BTld9unj.js} +3 -3
  23. package/dist/{app-BIsCEL65.js → app-4Qdv2_5b.js} +4 -4
  24. package/dist/{attach-node-helpers-CCWFS75V.js → attach-node-helpers-4d-YMaBH.js} +1 -1
  25. package/dist/{canvasNoteTools-CLMZ6u5m.js → canvasNoteTools-C9vJMuXY.js} +2 -2
  26. package/dist/executions.js +2 -2
  27. package/dist/{flow-builder-CIvz3sOr.js → flow-builder-ConlP_db.js} +60 -48
  28. package/dist/history.js +1 -1
  29. package/dist/{index-BDN8AK1D.d.ts → index-BVCOwqsN.d.ts} +90 -59
  30. package/dist/index.d.ts +2 -2
  31. package/dist/index.js +45 -45
  32. package/dist/{label-CDy86rIZ.js → label-BMVzJ2yW.js} +2 -2
  33. package/dist/{logs-panel-_Dl5Nrdn.js → logs-panel-BgGt_dFm.js} +1 -1
  34. package/dist/{node-detail-view-Dz1ZCIzV.js → node-detail-view-C4jNBsfW.js} +9 -9
  35. package/dist/{node-editor-tab-body-D5ybpm-b.js → node-editor-tab-body-CYeFTQr6.js} +3 -3
  36. package/dist/{node-operation-selection-bFx9s45-.js → node-operation-selection-DHbtGNrG.js} +9 -9
  37. package/dist/node-type-icon-DEAztACh.js +28 -0
  38. package/dist/nodes.js +1 -1
  39. package/dist/observability.d.ts +1 -1
  40. package/dist/observability.js +2 -2
  41. package/dist/{popover-ClSWhtja.js → popover-DhwmsszC.js} +4 -4
  42. package/dist/{quick-config-panel-iDMk4TT5.js → quick-config-panel-c8JQlXuc.js} +5 -5
  43. package/dist/{schedule-panel-DzkTZm2q.js → schedule-panel-CBab8kSL.js} +5 -5
  44. package/dist/settings.js +1 -1
  45. package/dist/{sidebar-node-editor-C16VnTzr.js → sidebar-node-editor-Bo_XRHJs.js} +5 -5
  46. package/dist/store-icon-BOiBSHY6.js +100 -0
  47. package/dist/template-preview-BpP8QMAe.js +32 -0
  48. package/dist/templates.js +2 -2
  49. package/dist/{useAgentDefinitions-akQ6WwzD.js → useAgentDefinitions-Bs_ci53W.js} +1 -1
  50. package/dist/{useDurableParity-fZcATZHI.js → useDurableParity-DrahdkaR.js} +1 -1
  51. package/dist/{useExecutions-lRqeXDYS.js → useExecutions-6P7ohzlh.js} +1 -1
  52. package/dist/{useGlobalStoreClient-BOI2kYRv.js → useGlobalStoreClient-B42oOe9Q.js} +1 -1
  53. package/dist/{useObservability-CBFLQBQO.js → useObservability-BSLLLev9.js} +1 -1
  54. package/dist/{useRunHistory-DSDD_ha3.js → useRunHistory-DuKiGSuF.js} +1 -1
  55. package/dist/{useWorkflowCodeExport-qGat2KNz.js → useWorkflowCodeExport-D80HIo-F.js} +1 -1
  56. package/dist/{useWorkflows-CXwXQg74.js → useWorkflows-B5kFIfrZ.js} +1 -1
  57. package/dist/workflow-builder.js +5 -5
  58. package/dist/workflow-pages.d.ts +1 -1
  59. package/dist/workflow-pages.js +15 -15
  60. package/dist/workflow-preview.js +1 -1
  61. package/dist/workflows-list.js +1 -1
  62. package/package.json +1 -1
  63. package/dist/node-type-icon-D28t8I64.js +0 -35
  64. package/dist/template-preview-C01KV277.js +0 -29
@@ -6,22 +6,22 @@ import { n as useManagedAiPolicyAuthority } from "./ManagedAiPolicyAuthorityCont
6
6
  import { c as budgetInputValue, d as withBudgetMode, f as EffectBadge, m as useActionPolicies, n as checkCodecServerUrl, p as effectLabel, s as BUDGET_DIMENSIONS, u as withBudgetLimit } from "./codec-client-CAYl6j46.js";
7
7
  import { i as isAutonomyLevel, o as useAutonomyLevelCopy, t as AUTONOMY_LEVELS } from "./run-mode-Du-fvjtE.js";
8
8
  import { n as getFluidGridsClient } from "./client-Ctl4jmY3.js";
9
- import { o as throwIfResultError, t as describeOperationError } from "./errors-xPZ-IbQd.js";
9
+ import { o as throwIfResultError, r as isForbiddenError, t as describeOperationError } from "./errors-xPZ-IbQd.js";
10
10
  import { d as useFluidGridsFlag, m as usePhaseCFlags, t as FLUIDGRIDS_FLAGS, u as useDurableParityEnabled } from "./feature-flags-B_b8PJ6Z.js";
11
+ import { QI as WorkflowSecuritySeverity, XI as WorkflowSearchAttributeType, ZI as WorkflowSecurityCheck } from "./graphql-types-D82O-0XY.js";
11
12
  import { M as saveCodecServerSettings, _ as fetchPauseRules, a as createPauseRule, c as deletePauseRule, f as fetchArchiveSettings, g as fetchCodecServerSettings, j as saveArchiveSettings, l as deleteSearchAttribute, o as createSearchAttribute, r as clearArchiveSettings, x as fetchSearchAttributes } from "./api-CEnNq15C.js";
12
13
  import { i as NativeSelect, o as StatusPill, t as FieldError, u as useResource } from "./shared-C0LCTtyh.js";
13
- import { XI as WorkflowSearchAttributeType } from "./graphql-types-D82O-0XY.js";
14
- import { A as useFlowStore } from "./node-operation-selection-bFx9s45-.js";
15
- import { Tt as useSaveStatusStore, Yt as findSaveConflict } from "./FluidGridsProvider-Dxehlm-2.js";
16
- import { a as useLogStreamDestinations, i as useLogStreamDestinationActions } from "./useObservability-CBFLQBQO.js";
14
+ import { A as useFlowStore } from "./node-operation-selection-DHbtGNrG.js";
15
+ import { Tt as useSaveStatusStore, Yt as findSaveConflict } from "./FluidGridsProvider-CPLfIarV.js";
16
+ import { a as useLogStreamDestinations, i as useLogStreamDestinationActions } from "./useObservability-BSLLLev9.js";
17
17
  import { n as useConnections } from "./useConnections-BlB0gbOB.js";
18
- import { Nt as useUpdateWorkflowDefinitionMutation, Rt as useWorkflowDefinitionQuery, Vt as useWorkflowDefinitionsQuery, n as useAgentDefinitions } from "./useAgentDefinitions-akQ6WwzD.js";
18
+ import { Nt as useUpdateWorkflowDefinitionMutation, Rt as useWorkflowDefinitionQuery, Vt as useWorkflowDefinitionsQuery, n as useAgentDefinitions } from "./useAgentDefinitions-Bs_ci53W.js";
19
19
  import { a as formToInput$2, i as emptyAgentForm, n as agentToForm, o as grantDays, r as describeRule, t as AgentFormError } from "./agent-form-D0uLvzx4.js";
20
20
  import { r as getFluidGridsConfig } from "./config-X97zrIvn.js";
21
21
  import { n as getGlobalStoreClient } from "./global-client-DP_Wxe5t.js";
22
22
  import { t as FluidGridsDocsLink } from "./FluidGridsDocsLink-JxkBpOeb.js";
23
23
  import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
24
- import { useNavigate, useParams } from "react-router";
24
+ import { Link, useNavigate, useParams } from "react-router";
25
25
  import { Icon } from "@iconify/react";
26
26
  import { cn } from "@burdenoff/fe-libs/utils";
27
27
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
@@ -712,6 +712,264 @@ const WorkflowNotificationSettingsDocument = {
712
712
  }]
713
713
  };
714
714
 
715
+ // src/generated/documents/wspace/WorkspaceSecurityReportDocument.ts
716
+ const WorkspaceSecurityReportDocument = {
717
+ "kind": "Document",
718
+ "definitions": [{
719
+ "kind": "OperationDefinition",
720
+ "operation": "query",
721
+ "name": {
722
+ "kind": "Name",
723
+ "value": "WorkspaceSecurityReport"
724
+ },
725
+ "variableDefinitions": [{
726
+ "kind": "VariableDefinition",
727
+ "variable": {
728
+ "kind": "Variable",
729
+ "name": {
730
+ "kind": "Name",
731
+ "value": "input"
732
+ }
733
+ },
734
+ "type": {
735
+ "kind": "NamedType",
736
+ "name": {
737
+ "kind": "Name",
738
+ "value": "WorkspaceSecurityReportInput"
739
+ }
740
+ },
741
+ "directives": []
742
+ }],
743
+ "directives": [],
744
+ "selectionSet": {
745
+ "kind": "SelectionSet",
746
+ "selections": [{
747
+ "kind": "Field",
748
+ "name": {
749
+ "kind": "Name",
750
+ "value": "workspaceSecurityReport"
751
+ },
752
+ "arguments": [{
753
+ "kind": "Argument",
754
+ "name": {
755
+ "kind": "Name",
756
+ "value": "input"
757
+ },
758
+ "value": {
759
+ "kind": "Variable",
760
+ "name": {
761
+ "kind": "Name",
762
+ "value": "input"
763
+ }
764
+ }
765
+ }],
766
+ "directives": [],
767
+ "selectionSet": {
768
+ "kind": "SelectionSet",
769
+ "selections": [
770
+ {
771
+ "kind": "Field",
772
+ "name": {
773
+ "kind": "Name",
774
+ "value": "generatedAt"
775
+ },
776
+ "arguments": [],
777
+ "directives": []
778
+ },
779
+ {
780
+ "kind": "Field",
781
+ "name": {
782
+ "kind": "Name",
783
+ "value": "checks"
784
+ },
785
+ "arguments": [],
786
+ "directives": []
787
+ },
788
+ {
789
+ "kind": "Field",
790
+ "name": {
791
+ "kind": "Name",
792
+ "value": "workflowsScanned"
793
+ },
794
+ "arguments": [],
795
+ "directives": []
796
+ },
797
+ {
798
+ "kind": "Field",
799
+ "name": {
800
+ "kind": "Name",
801
+ "value": "truncated"
802
+ },
803
+ "arguments": [],
804
+ "directives": []
805
+ },
806
+ {
807
+ "kind": "Field",
808
+ "name": {
809
+ "kind": "Name",
810
+ "value": "notes"
811
+ },
812
+ "arguments": [],
813
+ "directives": []
814
+ },
815
+ {
816
+ "kind": "Field",
817
+ "name": {
818
+ "kind": "Name",
819
+ "value": "counts"
820
+ },
821
+ "arguments": [],
822
+ "directives": [],
823
+ "selectionSet": {
824
+ "kind": "SelectionSet",
825
+ "selections": [{
826
+ "kind": "Field",
827
+ "name": {
828
+ "kind": "Name",
829
+ "value": "check"
830
+ },
831
+ "arguments": [],
832
+ "directives": []
833
+ }, {
834
+ "kind": "Field",
835
+ "name": {
836
+ "kind": "Name",
837
+ "value": "count"
838
+ },
839
+ "arguments": [],
840
+ "directives": []
841
+ }]
842
+ }
843
+ },
844
+ {
845
+ "kind": "Field",
846
+ "name": {
847
+ "kind": "Name",
848
+ "value": "findings"
849
+ },
850
+ "arguments": [],
851
+ "directives": [],
852
+ "selectionSet": {
853
+ "kind": "SelectionSet",
854
+ "selections": [
855
+ {
856
+ "kind": "Field",
857
+ "name": {
858
+ "kind": "Name",
859
+ "value": "check"
860
+ },
861
+ "arguments": [],
862
+ "directives": []
863
+ },
864
+ {
865
+ "kind": "Field",
866
+ "name": {
867
+ "kind": "Name",
868
+ "value": "severity"
869
+ },
870
+ "arguments": [],
871
+ "directives": []
872
+ },
873
+ {
874
+ "kind": "Field",
875
+ "name": {
876
+ "kind": "Name",
877
+ "value": "workflowKey"
878
+ },
879
+ "arguments": [],
880
+ "directives": []
881
+ },
882
+ {
883
+ "kind": "Field",
884
+ "name": {
885
+ "kind": "Name",
886
+ "value": "workflowName"
887
+ },
888
+ "arguments": [],
889
+ "directives": []
890
+ },
891
+ {
892
+ "kind": "Field",
893
+ "name": {
894
+ "kind": "Name",
895
+ "value": "version"
896
+ },
897
+ "arguments": [],
898
+ "directives": []
899
+ },
900
+ {
901
+ "kind": "Field",
902
+ "name": {
903
+ "kind": "Name",
904
+ "value": "active"
905
+ },
906
+ "arguments": [],
907
+ "directives": []
908
+ },
909
+ {
910
+ "kind": "Field",
911
+ "name": {
912
+ "kind": "Name",
913
+ "value": "nodeId"
914
+ },
915
+ "arguments": [],
916
+ "directives": []
917
+ },
918
+ {
919
+ "kind": "Field",
920
+ "name": {
921
+ "kind": "Name",
922
+ "value": "nodeType"
923
+ },
924
+ "arguments": [],
925
+ "directives": []
926
+ },
927
+ {
928
+ "kind": "Field",
929
+ "name": {
930
+ "kind": "Name",
931
+ "value": "nodeLabel"
932
+ },
933
+ "arguments": [],
934
+ "directives": []
935
+ },
936
+ {
937
+ "kind": "Field",
938
+ "name": {
939
+ "kind": "Name",
940
+ "value": "message"
941
+ },
942
+ "arguments": [],
943
+ "directives": []
944
+ },
945
+ {
946
+ "kind": "Field",
947
+ "name": {
948
+ "kind": "Name",
949
+ "value": "connectionId"
950
+ },
951
+ "arguments": [],
952
+ "directives": []
953
+ },
954
+ {
955
+ "kind": "Field",
956
+ "name": {
957
+ "kind": "Name",
958
+ "value": "latestVersion"
959
+ },
960
+ "arguments": [],
961
+ "directives": []
962
+ }
963
+ ]
964
+ }
965
+ }
966
+ ]
967
+ }
968
+ }]
969
+ }
970
+ }]
971
+ };
972
+
715
973
  // src/modules/action-policies/utils/policy-form.ts
716
974
  const POLICY_EFFECTS = [
717
975
  "REQUIRE_APPROVAL",
@@ -3108,6 +3366,555 @@ function WorkflowNotificationsTab({ workflowKey }) {
3108
3366
  });
3109
3367
  }
3110
3368
 
3369
+ // src/modules/settings/hooks/useWorkspaceSecurityReport.ts
3370
+ /**
3371
+ * The workspace security report (BOFF-9747) for Settings → Security report.
3372
+ *
3373
+ * `workspaceSecurityReport` is computed on demand from the stored workflow
3374
+ * definitions and is for workspace admins only: the gateway answers anyone
3375
+ * else with FORBIDDEN, which the hook reports as `forbidden` rather than as a
3376
+ * load failure, so the screen can say who may see the report.
3377
+ */
3378
+ async function fetchWorkspaceSecurityReport() {
3379
+ const result = await getFluidGridsClient().query({
3380
+ query: WorkspaceSecurityReportDocument,
3381
+ variables: {},
3382
+ fetchPolicy: "network-only"
3383
+ });
3384
+ throwIfResultError(result);
3385
+ const report = result.data?.workspaceSecurityReport;
3386
+ if (!report) throw new Error("The security report did not come back.");
3387
+ return report;
3388
+ }
3389
+ const LOADING = {
3390
+ report: null,
3391
+ error: null,
3392
+ forbidden: false,
3393
+ loading: true
3394
+ };
3395
+ function useWorkspaceSecurityReport() {
3396
+ const [state, setState] = useState(LOADING);
3397
+ const [generation, setGeneration] = useState(0);
3398
+ useEffect(() => {
3399
+ let cancelled = false;
3400
+ const load = async () => {
3401
+ try {
3402
+ const report = await fetchWorkspaceSecurityReport();
3403
+ if (!cancelled) setState({
3404
+ report,
3405
+ error: null,
3406
+ forbidden: false,
3407
+ loading: false
3408
+ });
3409
+ } catch (err) {
3410
+ if (!cancelled) setState((current) => ({
3411
+ report: current.report,
3412
+ error: err instanceof Error ? err : new Error(String(err)),
3413
+ forbidden: isForbiddenError(err),
3414
+ loading: false
3415
+ }));
3416
+ }
3417
+ };
3418
+ load();
3419
+ return () => {
3420
+ cancelled = true;
3421
+ };
3422
+ }, [generation]);
3423
+ const refresh = useCallback(() => {
3424
+ setState((current) => ({
3425
+ ...current,
3426
+ error: null,
3427
+ loading: true
3428
+ }));
3429
+ setGeneration((value) => value + 1);
3430
+ }, []);
3431
+ return {
3432
+ ...state,
3433
+ refresh
3434
+ };
3435
+ }
3436
+
3437
+ // src/modules/settings/utils/security-report.ts
3438
+ /** Every check the service runs, in the order the screen lists them. */
3439
+ const SECURITY_CHECKS = [
3440
+ "PUBLIC_TRIGGER_WITHOUT_AUTH",
3441
+ "CODE_EXECUTION",
3442
+ "HTTP_TO_PRIVATE_ADDRESS",
3443
+ "SQL_WITH_EXPRESSIONS",
3444
+ "OUTDATED_STORE_NODE"
3445
+ ];
3446
+ /** Severities, most serious first. */
3447
+ const SECURITY_SEVERITIES = [
3448
+ "HIGH",
3449
+ "MEDIUM",
3450
+ "LOW"
3451
+ ];
3452
+ /** The filter value that keeps every check or severity. */
3453
+ const ALL = "ALL";
3454
+ const CHECK_SET = new Set(SECURITY_CHECKS);
3455
+ const SEVERITY_SET = new Set(SECURITY_SEVERITIES);
3456
+ /** A select's value as a check filter; anything unknown keeps every check. */
3457
+ function toCheckFilter(value) {
3458
+ return SECURITY_CHECKS.find((check) => check === value) ?? "ALL";
3459
+ }
3460
+ /** A select's value as a severity filter; anything unknown keeps every severity. */
3461
+ function toSeverityFilter(value) {
3462
+ return SECURITY_SEVERITIES.find((severity) => severity === value) ?? "ALL";
3463
+ }
3464
+ /**
3465
+ * One row per known check, in screen order, with the service's count (taken
3466
+ * before the findings list is cut, so it can be larger than the findings
3467
+ * shown). Checks that did not run show 0 and `ran: false`.
3468
+ */
3469
+ function summarizeChecks(report) {
3470
+ const ran = new Set(report.checks);
3471
+ const counts = /* @__PURE__ */ new Map();
3472
+ for (const entry of report.counts) counts.set(entry.check, entry.count);
3473
+ return SECURITY_CHECKS.map((check) => ({
3474
+ check,
3475
+ count: counts.get(check) ?? 0,
3476
+ ran: ran.has(check)
3477
+ }));
3478
+ }
3479
+ /** The findings that match both filters, in the service's order (most severe first). */
3480
+ function filterFindings(findings, filter) {
3481
+ return findings.filter((finding) => (filter.check === "ALL" || finding.check === filter.check) && (filter.severity === "ALL" || finding.severity === filter.severity));
3482
+ }
3483
+ /** The badge tone for a severity; an unknown value reads as neutral. */
3484
+ function severityTone(severity) {
3485
+ if (!SEVERITY_SET.has(severity)) return "neutral";
3486
+ if (severity === String("HIGH")) return "danger";
3487
+ if (severity === String("MEDIUM")) return "warning";
3488
+ return "neutral";
3489
+ }
3490
+ /** True for a check this SDK knows a label for. */
3491
+ function isKnownCheck(check) {
3492
+ return CHECK_SET.has(check);
3493
+ }
3494
+ /**
3495
+ * Where a finding opens: the builder on the finding's workflow version. The
3496
+ * builder has no node deep link, so the node is named in the row instead.
3497
+ * Relative to the Settings page, like its "Back to Workflows" link.
3498
+ */
3499
+ function findingBuilderPath(finding) {
3500
+ return `../workflow/${encodeURIComponent(finding.workflowKey)}/${encodeURIComponent(finding.version)}`;
3501
+ }
3502
+ /** The node's name for a row: its label, else its id. */
3503
+ function findingNodeName(finding) {
3504
+ const label = finding.nodeLabel?.trim();
3505
+ return label ? label : finding.nodeId;
3506
+ }
3507
+ /** A stable key for a finding row. */
3508
+ function findingKey(finding) {
3509
+ return [
3510
+ finding.check,
3511
+ finding.workflowKey,
3512
+ finding.version,
3513
+ finding.nodeId,
3514
+ finding.connectionId ?? ""
3515
+ ].join("|");
3516
+ }
3517
+ /**
3518
+ * One unique key per finding row. Two findings with the same check, workflow,
3519
+ * version, node and connection would collide on {@link findingKey}; later
3520
+ * repeats get a `#n` suffix so React keys stay unique.
3521
+ */
3522
+ function uniqueFindingKeys(findings) {
3523
+ const seen = /* @__PURE__ */ new Map();
3524
+ return findings.map((finding) => {
3525
+ const key = findingKey(finding);
3526
+ const count = seen.get(key) ?? 0;
3527
+ seen.set(key, count + 1);
3528
+ return count === 0 ? key : `${key}#${count}`;
3529
+ });
3530
+ }
3531
+
3532
+ // src/modules/settings/utils/security-report-labels.ts
3533
+ const TONE_CLASSES$1 = {
3534
+ danger: "bg-status-error-bg-subtle text-status-error-text border-status-error-border",
3535
+ warning: "bg-status-warning-bg-subtle text-status-warning-text border-status-warning-border",
3536
+ neutral: "bg-surface text-text-secondary border-border-default"
3537
+ };
3538
+ function checkLabel(tr, check) {
3539
+ if (!isKnownCheck(check)) return check;
3540
+ return {
3541
+ ["PUBLIC_TRIGGER_WITHOUT_AUTH"]: tr("fluidgrids.settings.securityReport.check.publicTrigger", "Public trigger without auth"),
3542
+ ["CODE_EXECUTION"]: tr("fluidgrids.settings.securityReport.check.codeExecution", "Code execution"),
3543
+ ["HTTP_TO_PRIVATE_ADDRESS"]: tr("fluidgrids.settings.securityReport.check.httpPrivate", "HTTP to a private address"),
3544
+ ["SQL_WITH_EXPRESSIONS"]: tr("fluidgrids.settings.securityReport.check.sqlExpressions", "SQL built from expressions"),
3545
+ ["OUTDATED_STORE_NODE"]: tr("fluidgrids.settings.securityReport.check.outdatedNode", "Outdated Node Store node")
3546
+ }[check];
3547
+ }
3548
+ function severityLabel(tr, severity) {
3549
+ return {
3550
+ ["HIGH"]: tr("fluidgrids.settings.securityReport.severity.high", "High"),
3551
+ ["MEDIUM"]: tr("fluidgrids.settings.securityReport.severity.medium", "Medium"),
3552
+ ["LOW"]: tr("fluidgrids.settings.securityReport.severity.low", "Low")
3553
+ }[severity];
3554
+ }
3555
+
3556
+ // src/modules/settings/components/SecurityReportParts.tsx
3557
+ /**
3558
+ * Sections of Settings → Security report (BOFF-9747), split out of
3559
+ * SecurityReportTab so each part stays small.
3560
+ */
3561
+ function SeverityBadge({ severity, label }) {
3562
+ return /* @__PURE__ */ jsx(Badge, {
3563
+ variant: "outline",
3564
+ className: cn("text-xs", TONE_CLASSES$1[severityTone(severity)]),
3565
+ children: label
3566
+ });
3567
+ }
3568
+ function FindingRow({ finding, tr }) {
3569
+ return /* @__PURE__ */ jsxs("tr", {
3570
+ className: "border-t border-border-default align-top",
3571
+ "data-testid": "security-report-finding",
3572
+ "data-check": finding.check,
3573
+ "data-severity": finding.severity,
3574
+ children: [
3575
+ /* @__PURE__ */ jsx("td", {
3576
+ className: "px-3 py-2",
3577
+ children: /* @__PURE__ */ jsx(SeverityBadge, {
3578
+ severity: finding.severity,
3579
+ label: severityLabel(tr, finding.severity)
3580
+ })
3581
+ }),
3582
+ /* @__PURE__ */ jsx("td", {
3583
+ className: "px-3 py-2 text-text-primary",
3584
+ children: checkLabel(tr, finding.check)
3585
+ }),
3586
+ /* @__PURE__ */ jsxs("td", {
3587
+ className: "px-3 py-2",
3588
+ children: [/* @__PURE__ */ jsx(Link, {
3589
+ to: findingBuilderPath(finding),
3590
+ className: "font-medium text-text-primary underline-offset-2 hover:underline",
3591
+ "data-testid": "security-report-finding-link",
3592
+ children: finding.workflowName || finding.workflowKey
3593
+ }), /* @__PURE__ */ jsxs("div", {
3594
+ className: "text-xs text-text-muted",
3595
+ children: [
3596
+ tr("fluidgrids.settings.securityReport.versionLabel", "Version"),
3597
+ " ",
3598
+ finding.version,
3599
+ finding.active ? ` · ${tr("fluidgrids.settings.securityReport.activeLabel", "Active")}` : ` · ${tr("fluidgrids.settings.securityReport.draftLabel", "Draft")}`
3600
+ ]
3601
+ })]
3602
+ }),
3603
+ /* @__PURE__ */ jsxs("td", {
3604
+ className: "px-3 py-2",
3605
+ children: [/* @__PURE__ */ jsx("div", {
3606
+ className: "text-text-primary",
3607
+ children: findingNodeName(finding)
3608
+ }), /* @__PURE__ */ jsx("div", {
3609
+ className: "text-xs text-text-muted",
3610
+ children: finding.nodeType
3611
+ })]
3612
+ }),
3613
+ /* @__PURE__ */ jsxs("td", {
3614
+ className: "px-3 py-2 text-text-secondary",
3615
+ children: [finding.message, finding.latestVersion ? /* @__PURE__ */ jsx("div", {
3616
+ className: "text-xs text-text-muted",
3617
+ children: tr("fluidgrids.settings.securityReport.latestVersion", "Newest version: {{version}}", { version: finding.latestVersion })
3618
+ }) : null]
3619
+ })
3620
+ ]
3621
+ });
3622
+ }
3623
+ function ReportForbidden({ tr }) {
3624
+ return /* @__PURE__ */ jsxs("div", {
3625
+ className: "flex items-center gap-2 rounded-lg border border-border-default bg-surface p-3",
3626
+ "data-testid": "security-report-forbidden",
3627
+ role: "note",
3628
+ children: [/* @__PURE__ */ jsx(Icon, {
3629
+ icon: "mdi:shield-lock-outline",
3630
+ width: 18,
3631
+ className: "shrink-0 text-text-secondary"
3632
+ }), /* @__PURE__ */ jsx("p", {
3633
+ className: "text-sm text-text-secondary",
3634
+ children: tr("fluidgrids.settings.securityReport.adminsOnly", "Only workspace admins can see this report.")
3635
+ })]
3636
+ });
3637
+ }
3638
+ function ReportLoadFailed({ tr, error, onRetry }) {
3639
+ return /* @__PURE__ */ jsxs("div", {
3640
+ className: "space-y-3 rounded-lg border border-status-error-border bg-status-error-bg-subtle p-3",
3641
+ "data-testid": "security-report-error",
3642
+ role: "alert",
3643
+ children: [/* @__PURE__ */ jsxs("p", {
3644
+ className: "text-sm text-status-error-text",
3645
+ children: [
3646
+ tr("fluidgrids.settings.securityReport.loadFailed", "Could not load the security report."),
3647
+ " ",
3648
+ error.message
3649
+ ]
3650
+ }), /* @__PURE__ */ jsx(Button$1, {
3651
+ variant: "outline",
3652
+ size: "sm",
3653
+ onClick: onRetry,
3654
+ children: tr("fluidgrids.settings.securityReport.retry", "Try again")
3655
+ })]
3656
+ });
3657
+ }
3658
+ function ReportLoading({ tr }) {
3659
+ return /* @__PURE__ */ jsx("p", {
3660
+ className: "text-sm text-text-secondary",
3661
+ "data-testid": "security-report-loading",
3662
+ children: tr("fluidgrids.settings.securityReport.loading", "Checking the workspace's workflows…")
3663
+ });
3664
+ }
3665
+ function ReportHeader({ tr, report, loading, onRefresh }) {
3666
+ return /* @__PURE__ */ jsxs("div", {
3667
+ className: "flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between",
3668
+ children: [/* @__PURE__ */ jsxs("div", {
3669
+ className: "flex items-start gap-2",
3670
+ children: [/* @__PURE__ */ jsx(Icon, {
3671
+ icon: "mdi:shield-search",
3672
+ width: 18,
3673
+ className: "mt-0.5 shrink-0 text-text-secondary"
3674
+ }), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("p", {
3675
+ className: "text-sm text-text-secondary",
3676
+ children: tr("fluidgrids.settings.securityReport.intro", "Risky workflows of this workspace, checked from their saved definitions. Only workspace admins see this report.")
3677
+ }), /* @__PURE__ */ jsx("p", {
3678
+ className: "mt-1 text-xs text-text-muted",
3679
+ "data-testid": "security-report-meta",
3680
+ children: tr("fluidgrids.settings.securityReport.meta", "{{count}} workflows checked · {{time}}", {
3681
+ count: report.workflowsScanned,
3682
+ time: new Date(report.generatedAt).toLocaleString()
3683
+ })
3684
+ })] })]
3685
+ }), /* @__PURE__ */ jsxs(Button$1, {
3686
+ variant: "outline",
3687
+ size: "sm",
3688
+ onClick: onRefresh,
3689
+ disabled: loading,
3690
+ "data-testid": "security-report-refresh",
3691
+ children: [/* @__PURE__ */ jsx(Icon, {
3692
+ icon: "mdi:refresh",
3693
+ className: "mr-2",
3694
+ width: 16
3695
+ }), tr("fluidgrids.settings.securityReport.refresh", "Run again")]
3696
+ })]
3697
+ });
3698
+ }
3699
+ function ReportTruncatedNote({ tr }) {
3700
+ return /* @__PURE__ */ jsxs("div", {
3701
+ className: "flex items-start gap-2 rounded-lg border border-status-warning-border bg-status-warning-bg-subtle p-3",
3702
+ "data-testid": "security-report-truncated",
3703
+ role: "note",
3704
+ children: [/* @__PURE__ */ jsx(Icon, {
3705
+ icon: "mdi:alert-outline",
3706
+ width: 18,
3707
+ className: "mt-0.5 shrink-0 text-status-warning-text"
3708
+ }), /* @__PURE__ */ jsx("p", {
3709
+ className: "text-sm text-status-warning-text",
3710
+ children: tr("fluidgrids.settings.securityReport.truncated", "This report is incomplete: some workflows or findings were left out because the report is bounded. Counts still include them.")
3711
+ })]
3712
+ });
3713
+ }
3714
+ function CheckSummary({ tr, summary, check, onCheck }) {
3715
+ return /* @__PURE__ */ jsx("div", {
3716
+ className: "grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-5",
3717
+ "data-testid": "security-report-summary",
3718
+ children: summary.map((entry) => /* @__PURE__ */ jsxs("button", {
3719
+ type: "button",
3720
+ onClick: () => onCheck(check === entry.check ? "ALL" : toCheckFilter(entry.check)),
3721
+ "aria-pressed": check === entry.check,
3722
+ className: cn("rounded-lg border bg-surface p-3 text-left", check === entry.check ? "border-border-strong" : "border-border-default"),
3723
+ "data-testid": `security-report-count-${entry.check}`,
3724
+ children: [/* @__PURE__ */ jsx("div", {
3725
+ className: "text-2xl font-semibold text-text-primary",
3726
+ children: entry.ran ? entry.count : "–"
3727
+ }), /* @__PURE__ */ jsx("div", {
3728
+ className: "text-xs text-text-secondary",
3729
+ children: checkLabel(tr, entry.check)
3730
+ })]
3731
+ }, entry.check))
3732
+ });
3733
+ }
3734
+ function ReportFilters({ tr, check, severity, onCheck, onSeverity }) {
3735
+ const allLabel = tr("fluidgrids.settings.securityReport.filterAll", "All");
3736
+ return /* @__PURE__ */ jsxs("div", {
3737
+ className: "flex flex-col gap-3 sm:flex-row",
3738
+ children: [/* @__PURE__ */ jsxs(Select, {
3739
+ value: check,
3740
+ onValueChange: (value) => onCheck(toCheckFilter(value)),
3741
+ children: [/* @__PURE__ */ jsx(SelectTrigger, {
3742
+ className: "w-full bg-surface border-border-default sm:w-[260px]",
3743
+ "aria-label": tr("fluidgrids.settings.securityReport.checkFilter", "Check"),
3744
+ "data-testid": "security-report-check-filter",
3745
+ children: /* @__PURE__ */ jsx(SelectValue, {})
3746
+ }), /* @__PURE__ */ jsxs(SelectContent, { children: [/* @__PURE__ */ jsx(SelectItem, {
3747
+ value: "ALL",
3748
+ children: allLabel
3749
+ }), SECURITY_CHECKS.map((value) => /* @__PURE__ */ jsx(SelectItem, {
3750
+ value,
3751
+ children: checkLabel(tr, value)
3752
+ }, value))] })]
3753
+ }), /* @__PURE__ */ jsxs(Select, {
3754
+ value: severity,
3755
+ onValueChange: (value) => onSeverity(toSeverityFilter(value)),
3756
+ children: [/* @__PURE__ */ jsx(SelectTrigger, {
3757
+ className: "w-full bg-surface border-border-default sm:w-[180px]",
3758
+ "aria-label": tr("fluidgrids.settings.securityReport.severityFilter", "Severity"),
3759
+ "data-testid": "security-report-severity-filter",
3760
+ children: /* @__PURE__ */ jsx(SelectValue, {})
3761
+ }), /* @__PURE__ */ jsxs(SelectContent, { children: [/* @__PURE__ */ jsx(SelectItem, {
3762
+ value: "ALL",
3763
+ children: allLabel
3764
+ }), SECURITY_SEVERITIES.map((value) => /* @__PURE__ */ jsx(SelectItem, {
3765
+ value,
3766
+ children: severityLabel(tr, value)
3767
+ }, value))] })]
3768
+ })]
3769
+ });
3770
+ }
3771
+ function FindingsTable({ tr, findings, hasAnyFindings }) {
3772
+ if (findings.length === 0) return /* @__PURE__ */ jsxs("div", {
3773
+ className: "flex items-center gap-2 rounded-lg border border-border-default bg-surface-sunken p-4",
3774
+ "data-testid": "security-report-empty",
3775
+ children: [/* @__PURE__ */ jsx(Icon, {
3776
+ icon: "mdi:shield-check-outline",
3777
+ width: 18,
3778
+ className: "shrink-0 text-text-secondary"
3779
+ }), /* @__PURE__ */ jsx("p", {
3780
+ className: "text-sm text-text-secondary",
3781
+ children: !hasAnyFindings ? tr("fluidgrids.settings.securityReport.noFindings", "No risky workflows found.") : tr("fluidgrids.settings.securityReport.noMatches", "No findings match these filters.")
3782
+ })]
3783
+ });
3784
+ const rowKeys = uniqueFindingKeys(findings);
3785
+ return /* @__PURE__ */ jsx("div", {
3786
+ className: "overflow-x-auto rounded-lg border border-border-default bg-surface",
3787
+ children: /* @__PURE__ */ jsxs("table", {
3788
+ className: "w-full min-w-[720px] text-sm",
3789
+ "data-testid": "security-report-table",
3790
+ children: [/* @__PURE__ */ jsx("thead", {
3791
+ className: "bg-surface-sunken text-left text-xs text-text-muted",
3792
+ children: /* @__PURE__ */ jsxs("tr", { children: [
3793
+ /* @__PURE__ */ jsx("th", {
3794
+ className: "px-3 py-2 font-medium",
3795
+ children: tr("fluidgrids.settings.securityReport.severityColumn", "Severity")
3796
+ }),
3797
+ /* @__PURE__ */ jsx("th", {
3798
+ className: "px-3 py-2 font-medium",
3799
+ children: tr("fluidgrids.settings.securityReport.checkColumn", "Check")
3800
+ }),
3801
+ /* @__PURE__ */ jsx("th", {
3802
+ className: "px-3 py-2 font-medium",
3803
+ children: tr("fluidgrids.settings.securityReport.workflowColumn", "Workflow")
3804
+ }),
3805
+ /* @__PURE__ */ jsx("th", {
3806
+ className: "px-3 py-2 font-medium",
3807
+ children: tr("fluidgrids.settings.securityReport.nodeColumn", "Node")
3808
+ }),
3809
+ /* @__PURE__ */ jsx("th", {
3810
+ className: "px-3 py-2 font-medium",
3811
+ children: tr("fluidgrids.settings.securityReport.detailsColumn", "What to do")
3812
+ })
3813
+ ] })
3814
+ }), /* @__PURE__ */ jsx("tbody", { children: findings.map((finding, index) => /* @__PURE__ */ jsx(FindingRow, {
3815
+ finding,
3816
+ tr
3817
+ }, rowKeys[index])) })]
3818
+ })
3819
+ });
3820
+ }
3821
+ function ReportNotes({ tr, notes }) {
3822
+ if (notes.length === 0) return null;
3823
+ return /* @__PURE__ */ jsxs("div", {
3824
+ "data-testid": "security-report-notes",
3825
+ children: [/* @__PURE__ */ jsx("h3", {
3826
+ className: "mb-1 text-sm font-semibold text-text-primary",
3827
+ children: tr("fluidgrids.settings.securityReport.notesTitle", "About this report")
3828
+ }), /* @__PURE__ */ jsx("ul", {
3829
+ className: "list-disc space-y-1 pl-5 text-xs text-text-secondary",
3830
+ children: [...new Set(notes)].map((note) => /* @__PURE__ */ jsx("li", { children: note }, note))
3831
+ })]
3832
+ });
3833
+ }
3834
+ function ReportRefreshFailed({ tr, error }) {
3835
+ return /* @__PURE__ */ jsxs("p", {
3836
+ className: "text-sm text-status-error-text",
3837
+ "data-testid": "security-report-refresh-failed",
3838
+ role: "alert",
3839
+ children: [
3840
+ tr("fluidgrids.settings.securityReport.refreshFailed", "Could not run the report again. This is the last report."),
3841
+ " ",
3842
+ error.message
3843
+ ]
3844
+ });
3845
+ }
3846
+
3847
+ // src/modules/settings/components/SecurityReportTab.tsx
3848
+ /**
3849
+ * Settings → Security report (BOFF-9747): the workspace's risky workflows, the
3850
+ * FluidGrids counterpart of `n8n audit`. Public triggers without auth, code
3851
+ * execution, HTTP calls to private addresses, SQL built from expressions and
3852
+ * outdated Node Store nodes, computed on demand by `workspaceSecurityReport`.
3853
+ *
3854
+ * Workspace admins only: anyone else sees a short note, not an error.
3855
+ */
3856
+ function SecurityReportTab() {
3857
+ const tr = useTr();
3858
+ const { report, loading, error, forbidden, refresh } = useWorkspaceSecurityReport();
3859
+ const [check, setCheck] = useState("ALL");
3860
+ const [severity, setSeverity] = useState("ALL");
3861
+ const summary = useMemo(() => report ? summarizeChecks(report) : [], [report]);
3862
+ const findings = useMemo(() => report ? filterFindings(report.findings, {
3863
+ check,
3864
+ severity
3865
+ }) : [], [
3866
+ report,
3867
+ check,
3868
+ severity
3869
+ ]);
3870
+ if (forbidden) return /* @__PURE__ */ jsx(ReportForbidden, { tr });
3871
+ if (error && !report) return /* @__PURE__ */ jsx(ReportLoadFailed, {
3872
+ tr,
3873
+ error,
3874
+ onRetry: refresh
3875
+ });
3876
+ if (!report) return /* @__PURE__ */ jsx(ReportLoading, { tr });
3877
+ return /* @__PURE__ */ jsxs("div", {
3878
+ className: "space-y-6",
3879
+ "data-testid": "security-report",
3880
+ children: [
3881
+ /* @__PURE__ */ jsx(ReportHeader, {
3882
+ tr,
3883
+ report,
3884
+ loading,
3885
+ onRefresh: refresh
3886
+ }),
3887
+ error ? /* @__PURE__ */ jsx(ReportRefreshFailed, {
3888
+ tr,
3889
+ error
3890
+ }) : null,
3891
+ report.truncated ? /* @__PURE__ */ jsx(ReportTruncatedNote, { tr }) : null,
3892
+ /* @__PURE__ */ jsx(CheckSummary, {
3893
+ tr,
3894
+ summary,
3895
+ check,
3896
+ onCheck: setCheck
3897
+ }),
3898
+ /* @__PURE__ */ jsx(ReportFilters, {
3899
+ tr,
3900
+ check,
3901
+ severity,
3902
+ onCheck: setCheck,
3903
+ onSeverity: setSeverity
3904
+ }),
3905
+ /* @__PURE__ */ jsx(FindingsTable, {
3906
+ tr,
3907
+ findings,
3908
+ hasAnyFindings: report.findings.length > 0
3909
+ }),
3910
+ /* @__PURE__ */ jsx(ReportNotes, {
3911
+ tr,
3912
+ notes: report.notes
3913
+ })
3914
+ ]
3915
+ });
3916
+ }
3917
+
3111
3918
  // src/modules/settings/components/RunControlTab.tsx
3112
3919
  /**
3113
3920
  * Settings → Run control (BOFF-9906, Phase C): the workspace's activity pause
@@ -6697,6 +7504,15 @@ function SettingsPage({ workflowKey: workflowKeyProp } = {}) {
6697
7504
  width: 16
6698
7505
  }), tr("fluidgrids.settings.page.mcpTab", "MCP")]
6699
7506
  }),
7507
+ /* @__PURE__ */ jsxs(TabsTrigger, {
7508
+ value: "security-report",
7509
+ className: "gap-1.5",
7510
+ "data-testid": "fluidgrids-settings-security-report-tab",
7511
+ children: [/* @__PURE__ */ jsx(Icon, {
7512
+ icon: "mdi:shield-search",
7513
+ width: 16
7514
+ }), tr("fluidgrids.settings.page.securityReportTab", "Security report")]
7515
+ }),
6700
7516
  /* @__PURE__ */ jsxs(TabsTrigger, {
6701
7517
  value: "notifications",
6702
7518
  className: "gap-1.5",
@@ -6755,6 +7571,10 @@ function SettingsPage({ workflowKey: workflowKeyProp } = {}) {
6755
7571
  value: "mcp",
6756
7572
  children: /* @__PURE__ */ jsx(McpGatewayTab, { workflowKey })
6757
7573
  }),
7574
+ /* @__PURE__ */ jsx(TabsContent, {
7575
+ value: "security-report",
7576
+ children: /* @__PURE__ */ jsx(SecurityReportTab, {})
7577
+ }),
6758
7578
  /* @__PURE__ */ jsx(TabsContent, {
6759
7579
  value: "notifications",
6760
7580
  children: /* @__PURE__ */ jsx(WorkflowNotificationsTab, { workflowKey })
@@ -6772,4 +7592,4 @@ function SettingsPage({ workflowKey: workflowKeyProp } = {}) {
6772
7592
  });
6773
7593
  }
6774
7594
 
6775
- export { DurableHistoryCapsDocument as a, UpdateDurableHistoryCapsDocument as c, ManagedAiWorkspacePolicyDocument as i, SettingsPage_exports as n, UpdateWorkflowNotificationSettingsDocument as o, WorkflowNotificationSettingsDocument as r, UpdateManagedAiWorkspacePolicyDocument as s, SettingsPage as t };
7595
+ export { ManagedAiWorkspacePolicyDocument as a, UpdateManagedAiWorkspacePolicyDocument as c, WorkflowNotificationSettingsDocument as i, UpdateDurableHistoryCapsDocument as l, SettingsPage_exports as n, DurableHistoryCapsDocument as o, WorkspaceSecurityReportDocument as r, UpdateWorkflowNotificationSettingsDocument as s, SettingsPage as t };