@cobaltcore-dev/aurora 0.22.0 → 0.23.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 (131) hide show
  1. package/dist/client/{AuthProvider-p1e1DuKU.mjs → AuthProvider-CQ6Yppoh.mjs} +2 -2
  2. package/dist/client/{AuthProvider-p1e1DuKU.mjs.map → AuthProvider-CQ6Yppoh.mjs.map} +1 -1
  3. package/dist/client/{BucketToastNotifications-BGd2wVmh.mjs → BucketToastNotifications-Dq4dudkM.mjs} +224 -172
  4. package/dist/client/BucketToastNotifications-Dq4dudkM.mjs.map +1 -0
  5. package/dist/client/{ContentHeader-BIScbvb4.mjs → ContentHeader-9KZdhOoA.mjs} +2 -2
  6. package/dist/client/{ContentHeader-BIScbvb4.mjs.map → ContentHeader-9KZdhOoA.mjs.map} +1 -1
  7. package/dist/client/{DeleteFlavorModal-lFMExcq_.mjs → DeleteFlavorModal-Be2qZo4w.mjs} +2 -2
  8. package/dist/client/{DeleteFlavorModal-lFMExcq_.mjs.map → DeleteFlavorModal-Be2qZo4w.mjs.map} +1 -1
  9. package/dist/client/{DeletePcaModal-Dg9dej-H.mjs → DeletePcaModal-CIFsGWyj.mjs} +4 -4
  10. package/dist/client/{DeletePcaModal-Dg9dej-H.mjs.map → DeletePcaModal-CIFsGWyj.mjs.map} +1 -1
  11. package/dist/client/{DetailsInfo-DwCo9P1o.mjs → DetailsInfo-CHVD7d-W.mjs} +3 -3
  12. package/dist/client/{DetailsInfo-DwCo9P1o.mjs.map → DetailsInfo-CHVD7d-W.mjs.map} +1 -1
  13. package/dist/client/{FiltersInput-cOaaRpcQ.mjs → FiltersInput-DqT08Ib-.mjs} +2 -2
  14. package/dist/client/{FiltersInput-cOaaRpcQ.mjs.map → FiltersInput-DqT08Ib-.mjs.map} +1 -1
  15. package/dist/client/{FloatingIpActionModals-CDF8LVtX.mjs → FloatingIpActionModals-BcceUHcg.mjs} +4 -4
  16. package/dist/client/{FloatingIpActionModals-CDF8LVtX.mjs.map → FloatingIpActionModals-BcceUHcg.mjs.map} +1 -1
  17. package/dist/client/{ImageToastNotifications-Dzq6jNTm.mjs → ImageToastNotifications-BGyX2J6j.mjs} +5 -5
  18. package/dist/client/ImageToastNotifications-BGyX2J6j.mjs.map +1 -0
  19. package/dist/client/{RouteError-BmZRk_ss.mjs → RouteError-Rm08PcxN.mjs} +2 -2
  20. package/dist/client/{RouteError-BmZRk_ss.mjs.map → RouteError-Rm08PcxN.mjs.map} +1 -1
  21. package/dist/client/{SortInput-DjaEHvcO.mjs → SortInput-D_q_jDfe.mjs} +2 -2
  22. package/dist/client/{SortInput-DjaEHvcO.mjs.map → SortInput-D_q_jDfe.mjs.map} +1 -1
  23. package/dist/client/{StatusError-CLB026fg.mjs → StatusError-2ZU1gFpu.mjs} +2 -2
  24. package/dist/client/{StatusError-CLB026fg.mjs.map → StatusError-2ZU1gFpu.mjs.map} +1 -1
  25. package/dist/client/{_auth-Dut7CaMR.mjs → _auth-D5_QNUos.mjs} +2 -2
  26. package/dist/client/{_auth-Dut7CaMR.mjs.map → _auth-D5_QNUos.mjs.map} +1 -1
  27. package/dist/client/{_flavorId-DUWdFEMH.mjs → _flavorId-BsmqS9hP.mjs} +7 -7
  28. package/dist/client/{_flavorId-DUWdFEMH.mjs.map → _flavorId-BsmqS9hP.mjs.map} +1 -1
  29. package/dist/client/{_flavorId-BMJcnI7H.mjs → _flavorId-CFmkmg2Y.mjs} +2 -2
  30. package/dist/client/{_flavorId-BMJcnI7H.mjs.map → _flavorId-CFmkmg2Y.mjs.map} +1 -1
  31. package/dist/client/{_floatingIpId-KcWWq9ZW2.mjs → _floatingIpId-Bb9ZEJ6b.mjs} +6 -6
  32. package/dist/client/_floatingIpId-Bb9ZEJ6b.mjs.map +1 -0
  33. package/dist/client/{_floatingIpId-CjQLzpC5.mjs → _floatingIpId-Dzh6_JCI.mjs} +2 -2
  34. package/dist/client/{_floatingIpId-CjQLzpC5.mjs.map → _floatingIpId-Dzh6_JCI.mjs.map} +1 -1
  35. package/dist/client/{_imageId-BkEpwbCr.mjs → _imageId-C3As7sHE.mjs} +5 -5
  36. package/dist/client/{_imageId-BkEpwbCr.mjs.map → _imageId-C3As7sHE.mjs.map} +1 -1
  37. package/dist/client/{_pcaId-BcwGho7g.mjs → _pcaId-n0q0aDb6.mjs} +6 -6
  38. package/dist/client/{_pcaId-BcwGho7g.mjs.map → _pcaId-n0q0aDb6.mjs.map} +1 -1
  39. package/dist/client/{_projectId-hMtrqW6m.mjs → _projectId-BuBIFdgG.mjs} +3 -3
  40. package/dist/client/{_projectId-hMtrqW6m.mjs.map → _projectId-BuBIFdgG.mjs.map} +1 -1
  41. package/dist/client/{_projectId-CoMD6nq6.mjs → _projectId-CAQkJHZ4.mjs} +3 -3
  42. package/dist/client/{_projectId-CoMD6nq6.mjs.map → _projectId-CAQkJHZ4.mjs.map} +1 -1
  43. package/dist/client/_projectId-D-b7paXE.mjs +29 -0
  44. package/dist/client/_projectId-D-b7paXE.mjs.map +1 -0
  45. package/dist/client/{_projectId-D8FHe0gr.mjs → _projectId-DNZsNxJw.mjs} +3 -3
  46. package/dist/client/{_projectId-D8FHe0gr.mjs.map → _projectId-DNZsNxJw.mjs.map} +1 -1
  47. package/dist/client/{_securityGroupId-B5-gJe1u.mjs → _securityGroupId-DFdNKa2l.mjs} +641 -504
  48. package/dist/client/_securityGroupId-DFdNKa2l.mjs.map +1 -0
  49. package/dist/client/{_securityGroupId-fPJWtDPU.mjs → _securityGroupId-DRkY3Gt6.mjs} +2 -2
  50. package/dist/client/_securityGroupId-DRkY3Gt6.mjs.map +1 -0
  51. package/dist/client/{_storageType-CI5f4CsI.mjs → _storageType-Cn7TSOki.mjs} +2 -2
  52. package/dist/client/{_storageType-CI5f4CsI.mjs.map → _storageType-Cn7TSOki.mjs.map} +1 -1
  53. package/dist/client/{_storageType-DePX1Aah.mjs → _storageType-_8Ambhxc.mjs} +832 -843
  54. package/dist/client/_storageType-_8Ambhxc.mjs.map +1 -0
  55. package/dist/client/{about-B4sGnxeW.mjs → about-C8tTrQaW.mjs} +2 -2
  56. package/dist/client/{about-B4sGnxeW.mjs.map → about-C8tTrQaW.mjs.map} +1 -1
  57. package/dist/client/assets/objectDownload.worker-BAc5Au4B.js.map +1 -0
  58. package/dist/client/{aurora-Dp_NPGe8.mjs → aurora-jyvXcI4T.mjs} +2 -2
  59. package/dist/client/{aurora-Dp_NPGe8.mjs.map → aurora-jyvXcI4T.mjs.map} +1 -1
  60. package/dist/client/{build-DgeVmENN.mjs → build-CRLQyqu2.mjs} +2616 -3346
  61. package/dist/client/{build-DgeVmENN.mjs.map → build-CRLQyqu2.mjs.map} +1 -1
  62. package/dist/client/{flavors-DkGuWvge.mjs → flavors-BfTXqmoD.mjs} +6 -6
  63. package/dist/client/{flavors-DkGuWvge.mjs.map → flavors-BfTXqmoD.mjs.map} +1 -1
  64. package/dist/client/{flavors-DLxUiBhE.mjs → flavors-Dy-2mRGv.mjs} +2 -2
  65. package/dist/client/{flavors-DLxUiBhE.mjs.map → flavors-Dy-2mRGv.mjs.map} +1 -1
  66. package/dist/client/floatingips-zhNK6Xxn.mjs +562 -0
  67. package/dist/client/floatingips-zhNK6Xxn.mjs.map +1 -0
  68. package/dist/client/hooks/useAvailableViewportHeight.d.ts +30 -0
  69. package/dist/client/hooks/useVirtualizedTableBody.d.ts +39 -0
  70. package/dist/client/{hooks-D0wGUhRF.mjs → hooks-Bw3sHHOg.mjs} +2 -2
  71. package/dist/client/{hooks-D0wGUhRF.mjs.map → hooks-Bw3sHHOg.mjs.map} +1 -1
  72. package/dist/client/{images-h9i7S4LN.mjs → images-DPKUXryV.mjs} +2 -2
  73. package/dist/client/{images-h9i7S4LN.mjs.map → images-DPKUXryV.mjs.map} +1 -1
  74. package/dist/client/{images-D9nfOtSC.mjs → images-DhZnqUG_.mjs} +29 -29
  75. package/dist/client/images-DhZnqUG_.mjs.map +1 -0
  76. package/dist/client/images-NBf2bV43.mjs.map +1 -1
  77. package/dist/client/index.js +303 -312
  78. package/dist/client/index.js.map +1 -1
  79. package/dist/client/{objects-CGOfO00N.mjs → objects-B3mbk6V0.mjs} +1361 -1082
  80. package/dist/client/objects-B3mbk6V0.mjs.map +1 -0
  81. package/dist/client/{objects-B3Z8h4RU.mjs → objects-BQ8xvSW8.mjs} +2 -2
  82. package/dist/client/{objects-B3Z8h4RU.mjs.map → objects-BQ8xvSW8.mjs.map} +1 -1
  83. package/dist/client/{pca-Nppb8aVa.mjs → pca-B60iWWfG.mjs} +7 -7
  84. package/dist/client/{pca-Nppb8aVa.mjs.map → pca-B60iWWfG.mjs.map} +1 -1
  85. package/dist/client/{pca-C7cMT8fW.mjs → pca-D0kKxHp4.mjs} +2 -2
  86. package/dist/client/{pca-C7cMT8fW.mjs.map → pca-D0kKxHp4.mjs.map} +1 -1
  87. package/dist/client/projects-yiK0HGSA.mjs.map +1 -1
  88. package/dist/client/{routes-CaqfgBYu.mjs → routes-BOf-GDMM.mjs} +4 -4
  89. package/dist/client/{routes-CaqfgBYu.mjs.map → routes-BOf-GDMM.mjs.map} +1 -1
  90. package/dist/client/{routes-Bz4zb2H4.mjs → routes-CtfnI-aY.mjs} +2 -2
  91. package/dist/client/{routes-Bz4zb2H4.mjs.map → routes-CtfnI-aY.mjs.map} +1 -1
  92. package/dist/client/securitygroups-B8aLyFK7.mjs +17 -0
  93. package/dist/client/securitygroups-B8aLyFK7.mjs.map +1 -0
  94. package/dist/client/securitygroups-DRhFle2O.mjs +589 -0
  95. package/dist/client/securitygroups-DRhFle2O.mjs.map +1 -0
  96. package/dist/client/trpcClient-Di56hpVv.mjs +71 -0
  97. package/dist/client/trpcClient-Di56hpVv.mjs.map +1 -0
  98. package/dist/client/trpcClient.d.ts +40 -4
  99. package/dist/client/useListWithFiltering-aPTUNRaW.mjs +32 -0
  100. package/dist/client/useListWithFiltering-aPTUNRaW.mjs.map +1 -0
  101. package/dist/client/useSecurityGroupPermissions-BPS1ibWq.mjs +161 -0
  102. package/dist/client/useSecurityGroupPermissions-BPS1ibWq.mjs.map +1 -0
  103. package/dist/server/index.d.ts +56 -53
  104. package/dist/server/index.js +297 -20
  105. package/package.json +14 -14
  106. package/dist/client/BucketToastNotifications-BGd2wVmh.mjs.map +0 -1
  107. package/dist/client/EditSecurityGroupModal-BGPXglju.mjs +0 -137
  108. package/dist/client/EditSecurityGroupModal-BGPXglju.mjs.map +0 -1
  109. package/dist/client/ImageToastNotifications-Dzq6jNTm.mjs.map +0 -1
  110. package/dist/client/_floatingIpId-KcWWq9ZW2.mjs.map +0 -1
  111. package/dist/client/_projectId-MjFv839t.mjs +0 -25
  112. package/dist/client/_projectId-MjFv839t.mjs.map +0 -1
  113. package/dist/client/_securityGroupId-B5-gJe1u.mjs.map +0 -1
  114. package/dist/client/_securityGroupId-fPJWtDPU.mjs.map +0 -1
  115. package/dist/client/_storageType-DePX1Aah.mjs.map +0 -1
  116. package/dist/client/buildFilterParams-DoZzMKX9.mjs +0 -15
  117. package/dist/client/buildFilterParams-DoZzMKX9.mjs.map +0 -1
  118. package/dist/client/floatingips-DC5OrR8h.mjs +0 -434
  119. package/dist/client/floatingips-DC5OrR8h.mjs.map +0 -1
  120. package/dist/client/images-B5ktfqg6.mjs +0 -8
  121. package/dist/client/images-B5ktfqg6.mjs.map +0 -1
  122. package/dist/client/images-D9nfOtSC.mjs.map +0 -1
  123. package/dist/client/objects-CGOfO00N.mjs.map +0 -1
  124. package/dist/client/projects-DvSiI5Vg.mjs +0 -12
  125. package/dist/client/projects-DvSiI5Vg.mjs.map +0 -1
  126. package/dist/client/securitygroups-JMvcJLpb.mjs +0 -440
  127. package/dist/client/securitygroups-JMvcJLpb.mjs.map +0 -1
  128. package/dist/client/trpcClient-BxcNKnhv.mjs +0 -65
  129. package/dist/client/trpcClient-BxcNKnhv.mjs.map +0 -1
  130. package/dist/client/useListWithFiltering-BuhE81I6.mjs +0 -157
  131. package/dist/client/useListWithFiltering-BuhE81I6.mjs.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"securitygroups-DRhFle2O.mjs","names":["React","useState","Modal","Button","ModalFooter","ButtonRow","Message","TextInput","DeleteSecurityGroupDialog","isOpen","onClose","securityGroup","onDelete","isDeleting","error","useLingui","confirmationText","setConfirmationText","deleteWord","t","isDeleteEnabled","toLowerCase","securityGroupName","name","id","handleDelete","e","preventDefault","handleClose","open","onCancel","size","title","modalFooter","className","variant","onClick","disabled","data-testid","div","dismissible","p","strong","value","onChange","target","placeholder","autoComplete","DataGridCell","DataGridRow","PopupMenu","PopupMenuItem","PopupMenuOptions","Checkbox","SecurityGroupTableRow","securityGroup","sg","permissions","onEdit","onDelete","onViewDetails","isReadOnly","showSelectColumn","isSelected","onSelect","useLingui","BooleanValue","value","span","t","handleShowDetails","data-testid","id","onClick","className","e","stopPropagation","checked","onChange","div","p","name","description","shared","project_id","stateful","label","canUpdate","canDelete","useState","useEffect","useRef","DataGrid","DataGridHeadCell","DataGridRow","Stack","Spinner","useNavigate","useProjectId","EditSecurityGroupModal","DeleteSecurityGroupDialog","SecurityGroupTableRow","SecurityGroupListContainer","securityGroups","isLoading","isError","error","permissions","onDeleteSecurityGroup","isDeletingSecurityGroup","deleteError","onUpdateSecurityGroup","isUpdatingSecurityGroup","updateError","currentProjectId","hasAnyBulkAction","onClearUpdateError","useLingui","navigate","projectId","selectedSecurityGroup","setSelectedSecurityGroup","editModalOpen","setEditModalOpen","deleteDialogOpen","setDeleteDialogOpen","prevIsDeletingRef","prevIsUpdatingRef","handleEdit","sg","id","handleDelete","handleViewDetails","to","params","securityGroupId","closeEditModal","closeDeleteDialog","deletionJustFinished","current","updateJustFinished","className","distribution","alignment","direction","variant","size","message","t","length","columns","map","label","isReadOnly","Boolean","project_id","securityGroup","onEdit","onDelete","onViewDetails","showSelectColumn","isSelected","onSelect","open","onClose","onUpdate","data","Promise","resolve","isOpen","isDeleting","React","useState","Modal","Form","FormRow","FormSection","TextInput","Checkbox","Button","ButtonRow","Spinner","ModalFooter","Textarea","Message","defaultSecurityGroupValues","name","description","stateful","CreateSecurityGroupModal","isOpen","onClose","onCreate","isLoading","error","useLingui","properties","setProperties","errors","setErrors","handleInputChange","e","value","type","target","checked","prev","newErrors","validateForm","trim","t","Object","keys","length","handleSubmit","preventDefault","securityGroupData","undefined","handleClose","open","onCancel","size","title","modalFooter","className","variant","onClick","disabled","data-testid","dismissible","div","span","id","label","onChange","required","errortext","placeholder","rows","helptext","parseFiltersFromUrl","searchParams","filters","shared","push","name","value","buildFilterParams","selectedFilters","filterDefinitions","params","length","filterGroups","filter","sf","inactive","reduce","acc","Object","entries","forEach","filterName","values","filterDef","find","f","supportsMultiValue","join","applyFilterSelection","current","selected","alreadySelected","some","supportsMulti","buildUrlSearchParams","baseParams","newParams","search","sortBy","sortDirection","useState","useEffect","useNavigate","useSearch","Button","Stack","DataGridToolbar","SearchInput","trpcReact","SortInput","SelectedFilters","FiltersInput","SecurityGroupListContainer","CreateSecurityGroupModal","parseFiltersFromUrl","buildFilterParams","buildUrlSearchParams","applyFilterSelection","useSecurityGroupPermissions","SecurityGroups","project","projectId","useLingui","navigate","searchParams","strict","sortSettings","setSortSettings","options","label","t","value","sortBy","sortDirection","filterSettings","setFilterSettings","filters","displayName","filterName","values","supportsMultiValue","selectedFilters","searchTerm","setSearchTerm","search","createModalOpen","setCreateModalOpen","deleteError","setDeleteError","createError","setCreateError","updateError","setUpdateError","utils","useUtils","prev","shared","urlFilters","urlSortBy","urlSortDirection","urlSearchTerm","data","securityGroupsData","isLoading","isError","error","network","securityGroup","list","useQuery","project_id","sort_key","sort_dir","refetchOnWindowFocus","securityGroups","listError","code","permissions","createSecurityGroupMutation","create","useMutation","onSuccess","createdSecurityGroup","invalidate","getById","to","params","securityGroupId","id","onError","message","deleteSecurityGroupMutation","deleteById","updateSecurityGroupMutation","update","handleCreateSecurityGroup","securityGroupData","mutateAsync","handleDeleteSecurityGroup","mutate","handleUpdateSecurityGroup","handleClearUpdateError","handleSortChange","newSortSettings","settings","toString","replace","handleFilterChange","newFilterSettings","handleSearchChange","term","searchValue","undefined","className","distribution","alignment","direction","length","gap","onSortByChange","v","onSortDirectionChange","dir","canCreate","onClick","variant","onChange","selected","newSelected","placeholder","data-testid","onInput","e","currentTarget","onSearch","onClear","onDelete","filterToRemove","filter","f","name","onCreateClick","onDeleteSecurityGroup","isDeletingSecurityGroup","isPending","onUpdateSecurityGroup","isUpdatingSecurityGroup","currentProjectId","hasAnyBulkAction","onClearUpdateError","isOpen","onClose","onCreate","useLingui","SecurityGroups","ContentHeading","Route","RouteComponent","t","projectId","useParams","trpcClient","useRouteContext","Error","component"],"sources":["../../src/client/routes/_auth/projects/$projectId/network/securitygroups/-components/-modals/DeleteSecurityGroupDialog.tsx","../../src/client/routes/_auth/projects/$projectId/network/securitygroups/-components/SecurityGroupTableRow.tsx","../../src/client/routes/_auth/projects/$projectId/network/securitygroups/-components/SecurityGroupListContainer.tsx","../../src/client/routes/_auth/projects/$projectId/network/securitygroups/-components/-modals/CreateSecurityGroupModal.tsx","../../src/client/routes/_auth/projects/$projectId/network/securitygroups/urlHelpers.ts","../../src/client/routes/_auth/projects/$projectId/network/securitygroups/-components/SecurityGroupsList.tsx","../../src/client/routes/_auth/projects/$projectId/network/securitygroups/index.tsx?tsr-split=component"],"sourcesContent":["import React, { useState } from \"react\"\nimport { Modal, Button, ModalFooter, ButtonRow, Message, TextInput } from \"@cloudoperators/juno-ui-components\"\nimport { Trans, useLingui } from \"@lingui/react/macro\"\nimport type { SecurityGroup } from \"@/server/Network/types/securityGroup\"\n\ninterface DeleteSecurityGroupDialogProps {\n isOpen: boolean\n securityGroup: SecurityGroup\n onClose: () => void\n onDelete: (securityGroupId: string) => void\n isDeleting?: boolean\n error?: string | null\n}\n\nexport const DeleteSecurityGroupDialog: React.FC<DeleteSecurityGroupDialogProps> = ({\n isOpen,\n onClose,\n securityGroup,\n onDelete,\n isDeleting = false,\n error = null,\n}) => {\n const { t } = useLingui()\n const [confirmationText, setConfirmationText] = useState(\"\")\n\n const deleteWord = t`delete`\n const isDeleteEnabled = confirmationText.toLowerCase() === deleteWord.toLowerCase()\n const securityGroupName = securityGroup.name || securityGroup.id\n\n const handleDelete = (e: React.MouseEvent<HTMLElement>) => {\n e.preventDefault()\n if (isDeleteEnabled && !isDeleting) {\n onDelete(securityGroup.id)\n }\n }\n\n const handleClose = () => {\n setConfirmationText(\"\")\n onClose()\n }\n\n return (\n <Modal\n open={isOpen}\n onCancel={handleClose}\n size=\"small\"\n title={t`Delete Security Group \"${securityGroupName}\"`}\n modalFooter={\n <ModalFooter className=\"flex justify-end\">\n <ButtonRow>\n <Button variant=\"default\" onClick={handleClose} disabled={isDeleting}>\n <Trans>Cancel</Trans>\n </Button>\n <Button\n variant=\"primary-danger\"\n onClick={handleDelete}\n disabled={!isDeleteEnabled || isDeleting}\n data-testid=\"confirm-delete-button\"\n >\n {isDeleting ? <Trans>Deleting...</Trans> : <Trans>Delete</Trans>}\n </Button>\n </ButtonRow>\n </ModalFooter>\n }\n >\n <div>\n {/* Error Message */}\n {error && (\n <Message dismissible={false} variant=\"error\" className=\"mt-4\">\n {error}\n </Message>\n )}\n\n {/* Warning */}\n <Trans>This action cannot be undone. The security group will be permanently deleted.</Trans>\n\n {/* Confirmation Input */}\n <div className=\"mt-4\">\n <p className=\"mb-2 text-sm\">\n <Trans>\n Type <strong>{deleteWord}</strong> to confirm:\n </Trans>\n </p>\n <TextInput\n id=\"confirmation\"\n name=\"confirmation\"\n value={confirmationText}\n onChange={(e) => setConfirmationText(e.target.value)}\n placeholder={deleteWord}\n disabled={isDeleting}\n autoComplete=\"off\"\n data-testid=\"delete-confirmation-input\"\n />\n </div>\n </div>\n </Modal>\n )\n}\n","import {\n DataGridCell,\n DataGridRow,\n PopupMenu,\n PopupMenuItem,\n PopupMenuOptions,\n Checkbox,\n} from \"@cloudoperators/juno-ui-components\"\nimport { useLingui, Trans } from \"@lingui/react/macro\"\nimport type { SecurityGroup } from \"@/server/Network/types/securityGroup\"\n\nexport interface SecurityGroupPermissions {\n canView: boolean\n canCreate: boolean\n canUpdate: boolean\n canDelete: boolean\n canCreateRule: boolean\n canDeleteRule: boolean\n canManageAccess: boolean\n canViewRBAC: boolean\n}\n\ninterface SecurityGroupTableRowProps {\n securityGroup: SecurityGroup\n permissions: SecurityGroupPermissions\n onEdit: (sg: SecurityGroup) => void\n onDelete: (sg: SecurityGroup) => void\n onViewDetails?: (sg: SecurityGroup) => void\n isReadOnly?: boolean\n showSelectColumn?: boolean\n isSelected?: boolean\n onSelect?: (sg: SecurityGroup) => void\n}\n\nexport function SecurityGroupTableRow({\n securityGroup: sg,\n permissions,\n onEdit,\n onDelete,\n onViewDetails,\n isReadOnly = false,\n showSelectColumn = false,\n isSelected = false,\n onSelect,\n}: SecurityGroupTableRowProps) {\n const { t } = useLingui()\n\n const BooleanValue = ({ value }: { value: boolean | undefined }) => <span>{value ? t`Yes` : t`No`}</span>\n\n const handleShowDetails = () => {\n if (onViewDetails) {\n onViewDetails(sg)\n }\n }\n\n return (\n <DataGridRow\n key={sg.id}\n data-testid={`security-group-row-${sg.id}`}\n onClick={handleShowDetails}\n className=\"hover:bg-theme-background-lvl-2 cursor-pointer\"\n >\n {showSelectColumn && (\n <DataGridCell onClick={(e) => e.stopPropagation()}>\n <Checkbox checked={isSelected} onChange={() => onSelect?.(sg)} />\n </DataGridCell>\n )}\n <DataGridCell>\n <div>\n <p className=\"text-md\">{sg.name}</p>\n <p className=\"text-theme-light text-xs\">{sg.id}</p>\n </div>\n </DataGridCell>\n <DataGridCell>{sg.description || t`—`}</DataGridCell>\n <DataGridCell>\n <BooleanValue value={sg.shared} />\n {sg.shared && (\n <p>\n <Trans>Owner</Trans>: <span className=\"text-theme-light text-xs\">{sg.project_id}</span>\n </p>\n )}\n </DataGridCell>\n <DataGridCell>\n <BooleanValue value={sg.stateful} />\n </DataGridCell>\n <DataGridCell onClick={(e) => e.stopPropagation()} className=\"items-end justify-end pr-0\">\n <PopupMenu>\n <PopupMenuOptions>\n <PopupMenuItem label={t`Show Details`} onClick={() => handleShowDetails()} />\n {permissions.canUpdate && !isReadOnly && <PopupMenuItem label={t`Edit`} onClick={() => onEdit(sg)} />}\n {permissions.canDelete && !isReadOnly && <PopupMenuItem label={t`Delete`} onClick={() => onDelete(sg)} />}\n </PopupMenuOptions>\n </PopupMenu>\n </DataGridCell>\n </DataGridRow>\n )\n}\n","import { useState, useEffect, useRef } from \"react\"\nimport { DataGrid, DataGridHeadCell, DataGridRow, Stack, Spinner } from \"@cloudoperators/juno-ui-components\"\nimport { Trans, useLingui } from \"@lingui/react/macro\"\nimport { useNavigate } from \"@tanstack/react-router\"\nimport { useProjectId } from \"@/client/hooks\"\nimport type { SecurityGroup } from \"@/server/Network/types/securityGroup\"\nimport type { UpdateSecurityGroupInput } from \"@/server/Network/types/securityGroup\"\nimport { EditSecurityGroupModal } from \"./-modals/EditSecurityGroupModal\"\nimport { DeleteSecurityGroupDialog } from \"./-modals/DeleteSecurityGroupDialog\"\nimport { SecurityGroupTableRow, type SecurityGroupPermissions } from \"./SecurityGroupTableRow\"\n\ninterface SecurityGroupListContainerProps {\n securityGroups: SecurityGroup[]\n isLoading: boolean\n isError: boolean\n error: { message?: string } | null\n permissions: SecurityGroupPermissions\n onCreateClick?: () => void\n onDeleteSecurityGroup?: (securityGroupId: string) => void\n isDeletingSecurityGroup?: boolean\n deleteError?: string | null\n onUpdateSecurityGroup?: (\n securityGroupId: string,\n data: Omit<UpdateSecurityGroupInput, \"securityGroupId\" | \"project_id\">\n ) => Promise<void>\n isUpdatingSecurityGroup?: boolean\n updateError?: string | null\n currentProjectId?: string\n hasAnyBulkAction?: boolean\n onClearUpdateError?: () => void\n}\n\nexport const SecurityGroupListContainer = ({\n securityGroups,\n isLoading,\n isError,\n error,\n permissions,\n onDeleteSecurityGroup,\n isDeletingSecurityGroup = false,\n deleteError = null,\n onUpdateSecurityGroup,\n isUpdatingSecurityGroup = false,\n updateError = null,\n currentProjectId,\n hasAnyBulkAction = false,\n onClearUpdateError,\n}: SecurityGroupListContainerProps) => {\n const { t } = useLingui()\n const navigate = useNavigate()\n const projectId = useProjectId()\n const [selectedSecurityGroup, setSelectedSecurityGroup] = useState<SecurityGroup | null>(null)\n const [editModalOpen, setEditModalOpen] = useState(false)\n const [deleteDialogOpen, setDeleteDialogOpen] = useState(false)\n const prevIsDeletingRef = useRef<boolean>(false)\n const prevIsUpdatingRef = useRef<boolean>(false)\n\n const handleEdit = (sg: SecurityGroup) => {\n // Clear error when selecting a different security group\n if (selectedSecurityGroup && selectedSecurityGroup.id !== sg.id && onClearUpdateError) {\n onClearUpdateError()\n }\n setSelectedSecurityGroup(sg)\n setEditModalOpen(true)\n }\n\n const handleDelete = (sg: SecurityGroup) => {\n setSelectedSecurityGroup(sg)\n setDeleteDialogOpen(true)\n }\n\n const handleViewDetails = (sg: SecurityGroup) => {\n navigate({\n to: \"/projects/$projectId/network/securitygroups/$securityGroupId\",\n params: { projectId, securityGroupId: sg.id },\n })\n }\n\n const closeEditModal = () => {\n setSelectedSecurityGroup(null)\n setEditModalOpen(false)\n }\n\n const closeDeleteDialog = () => {\n setSelectedSecurityGroup(null)\n setDeleteDialogOpen(false)\n }\n\n // Close delete dialog when deletion completes successfully\n useEffect(() => {\n // Check if deletion just finished (was deleting before, now not deleting)\n const deletionJustFinished = prevIsDeletingRef.current && !isDeletingSecurityGroup\n\n // Close dialog if deletion just finished and there's no error\n if (deletionJustFinished && deleteDialogOpen && !deleteError) {\n closeDeleteDialog()\n }\n\n // Update the ref for next render\n prevIsDeletingRef.current = isDeletingSecurityGroup\n }, [isDeletingSecurityGroup, deleteError, deleteDialogOpen])\n\n // Close edit modal when update completes successfully\n useEffect(() => {\n // Check if update just finished (was updating before, now not updating)\n const updateJustFinished = prevIsUpdatingRef.current && !isUpdatingSecurityGroup\n\n // Close modal if update just finished and there's no error\n if (updateJustFinished && editModalOpen && !updateError) {\n closeEditModal()\n }\n\n // Update the ref for next render\n prevIsUpdatingRef.current = isUpdatingSecurityGroup\n }, [isUpdatingSecurityGroup, updateError, editModalOpen])\n\n // Loading state\n if (isLoading) {\n return (\n <Stack className=\"py-8\" distribution=\"center\" alignment=\"center\" direction=\"vertical\">\n <Spinner variant=\"primary\" size=\"large\" className=\"mb-2\" />\n <Trans>Loading...</Trans>\n </Stack>\n )\n }\n\n // Error state\n if (isError) {\n return (\n <Stack className=\"py-8\" distribution=\"center\" alignment=\"center\" direction=\"vertical\">\n {error?.message ?? t`Failed to load security groups`}\n </Stack>\n )\n }\n\n // Empty state\n if (securityGroups.length === 0) {\n return <Trans>There are no groups</Trans>\n }\n\n return (\n <>\n <DataGrid columns={hasAnyBulkAction ? 6 : 5}>\n <DataGridRow>\n {hasAnyBulkAction && <DataGridHeadCell />}\n {[t`Name`, t`Description`, t`Shared`, t`Stateful`, \"\"].map((label) => (\n <DataGridHeadCell key={label}>{label}</DataGridHeadCell>\n ))}\n </DataGridRow>\n {securityGroups.map((sg) => {\n // Compute isReadOnly only when the security group has an explicit project owner\n const isReadOnly = Boolean(currentProjectId && sg.project_id && sg.project_id !== currentProjectId)\n\n return (\n <SecurityGroupTableRow\n key={sg.id}\n securityGroup={sg}\n permissions={permissions}\n onEdit={handleEdit}\n onDelete={handleDelete}\n onViewDetails={handleViewDetails}\n isReadOnly={isReadOnly}\n showSelectColumn={hasAnyBulkAction}\n isSelected={false}\n onSelect={() => {}}\n />\n )\n })}\n </DataGrid>\n\n {selectedSecurityGroup && (\n <>\n <EditSecurityGroupModal\n securityGroup={selectedSecurityGroup}\n open={editModalOpen}\n onClose={closeEditModal}\n onUpdate={(id, data) => {\n if (onUpdateSecurityGroup) {\n return onUpdateSecurityGroup(id, data)\n }\n return Promise.resolve()\n }}\n isLoading={isUpdatingSecurityGroup}\n error={updateError}\n />\n <DeleteSecurityGroupDialog\n securityGroup={selectedSecurityGroup}\n isOpen={deleteDialogOpen}\n onClose={closeDeleteDialog}\n onDelete={(id) => {\n if (onDeleteSecurityGroup) {\n onDeleteSecurityGroup(id)\n }\n }}\n isDeleting={isDeletingSecurityGroup}\n error={deleteError}\n />\n </>\n )}\n </>\n )\n}\n","import React, { useState } from \"react\"\nimport { Trans, useLingui } from \"@lingui/react/macro\"\nimport {\n Modal,\n Form,\n FormRow,\n FormSection,\n TextInput,\n Checkbox,\n Button,\n ButtonRow,\n Spinner,\n ModalFooter,\n Textarea,\n Message,\n} from \"@cloudoperators/juno-ui-components\"\nimport { CreateSecurityGroupInput } from \"@/server/Network/types/securityGroup\"\n\ninterface CreateSecurityGroupModalProps {\n isOpen: boolean\n onClose: () => void\n onCreate: (securityGroupData: Omit<CreateSecurityGroupInput, \"project_id\">) => Promise<void>\n isLoading?: boolean\n error?: string | null\n}\n\ninterface SecurityGroupProperties {\n name: string\n description: string\n stateful: boolean\n}\n\nconst defaultSecurityGroupValues: SecurityGroupProperties = {\n name: \"\",\n description: \"\",\n stateful: true,\n}\n\nexport const CreateSecurityGroupModal: React.FC<CreateSecurityGroupModalProps> = ({\n isOpen,\n onClose,\n onCreate,\n isLoading = false,\n error = null,\n}) => {\n const { t } = useLingui()\n\n const [properties, setProperties] = useState<SecurityGroupProperties>({ ...defaultSecurityGroupValues })\n const [errors, setErrors] = useState<{ [key: string]: string }>({})\n\n const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {\n const { name, value, type } = e.target\n const checked = (e.target as HTMLInputElement).checked\n\n setProperties((prev) => ({\n ...prev,\n [name]: type === \"checkbox\" ? checked : value,\n }))\n\n if (errors[name]) {\n setErrors((prev) => {\n const newErrors = { ...prev }\n delete newErrors[name]\n return newErrors\n })\n }\n }\n\n const validateForm = (): boolean => {\n const newErrors: { [key: string]: string } = {}\n\n if (!properties.name || properties.name.trim() === \"\") {\n newErrors.name = t`Security group name is required`\n }\n\n setErrors(newErrors)\n return Object.keys(newErrors).length === 0\n }\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault()\n\n if (!validateForm()) {\n return\n }\n\n const securityGroupData: Omit<CreateSecurityGroupInput, \"project_id\"> = {\n name: properties.name.trim(),\n description: properties.description.trim() || undefined,\n stateful: properties.stateful,\n }\n\n await onCreate(securityGroupData)\n handleClose()\n }\n\n const handleClose = () => {\n setProperties({ ...defaultSecurityGroupValues })\n setErrors({})\n onClose()\n }\n\n return (\n <Modal\n open={isOpen}\n onCancel={handleClose}\n size=\"large\"\n title={t`Create Security Group`}\n modalFooter={\n <ModalFooter className=\"flex justify-end\">\n <ButtonRow>\n <Button variant=\"default\" onClick={handleClose} disabled={isLoading}>\n <Trans>Cancel</Trans>\n </Button>\n <Button\n variant=\"primary\"\n onClick={(e) => {\n handleSubmit(e)\n }}\n disabled={isLoading}\n data-testid=\"create-security-group-button\"\n >\n {isLoading ? <Spinner size=\"small\" /> : <Trans>Create Security Group</Trans>}\n </Button>\n </ButtonRow>\n </ModalFooter>\n }\n >\n {/* Error Message */}\n {error && (\n <Message dismissible={false} variant=\"error\" className=\"mb-4\">\n {error}\n </Message>\n )}\n\n {isLoading && (\n <div className=\"mb-4 flex items-center justify-center gap-2\">\n <Spinner variant=\"primary\" />\n <span className=\"text-theme-high text-sm\">\n <Trans>Creating security group...</Trans>\n </span>\n </div>\n )}\n\n {!isLoading && (\n <Form className=\"mb-6\">\n <FormSection className=\"mb-6\">\n <FormRow className=\"mb-6\">\n <TextInput\n id=\"name\"\n name=\"name\"\n label={t`Name`}\n value={properties.name}\n onChange={handleInputChange}\n required\n errortext={errors.name}\n placeholder={t`Type name`}\n disabled={isLoading}\n />\n </FormRow>\n\n <FormRow className=\"mb-6\">\n <Textarea\n id=\"description\"\n name=\"description\"\n label={t`Description`}\n value={properties.description}\n onChange={handleInputChange}\n placeholder={t`Description`}\n disabled={isLoading}\n rows={3}\n />\n </FormRow>\n\n <FormRow className=\"mb-0\">\n <Checkbox\n id=\"stateful\"\n name=\"stateful\"\n label={t`Stateful`}\n checked={properties.stateful}\n onChange={handleInputChange}\n disabled={isLoading}\n helptext={t`Stateful security groups track connection state. This setting cannot be changed after creation.`}\n />\n </FormRow>\n </FormSection>\n </Form>\n )}\n </Modal>\n )\n}\n","import { SelectedFilter, Filter } from \"@/client/components/ListToolbar/types\"\n\ntype SecurityGroupsSearchParams = {\n shared?: string\n search?: string\n sortBy?: string\n sortDirection?: \"asc\" | \"desc\"\n}\n\n/**\n * Parses URL search params into SelectedFilter array for ListToolbar\n */\nexport const parseFiltersFromUrl = (searchParams: SecurityGroupsSearchParams): SelectedFilter[] => {\n const filters: SelectedFilter[] = []\n\n // Shared filter\n if (searchParams.shared) {\n filters.push({ name: \"shared\", value: searchParams.shared })\n }\n\n return filters\n}\n\n/**\n * Builds filter parameters for API request from SelectedFilter array\n */\nexport const buildFilterParams = (\n selectedFilters: SelectedFilter[],\n filterDefinitions: Filter[]\n): Record<string, string> => {\n const params: Record<string, string> = {}\n\n if (!selectedFilters?.length) return params\n\n // Group selected filters by filter name\n const filterGroups = selectedFilters\n .filter((sf) => !sf.inactive)\n .reduce(\n (acc, sf) => {\n if (!acc[sf.name]) acc[sf.name] = []\n acc[sf.name].push(sf.value)\n return acc\n },\n {} as Record<string, string[]>\n )\n\n // Build parameters based on whether filter supports multiple values\n Object.entries(filterGroups).forEach(([filterName, values]) => {\n const filterDef = filterDefinitions.find((f) => f.filterName === filterName)\n\n if (filterDef?.supportsMultiValue && values.length > 1) {\n params[filterName] = `in:${values.join(\",\")}`\n } else {\n params[filterName] = values[0]\n }\n })\n\n return params\n}\n\n/**\n * Merges a newly selected filter into the current list.\n * - Ignores duplicates (same name + value).\n * - For single-value filters, replaces any existing entry with the same name.\n * - For multi-value filters, appends.\n */\nexport const applyFilterSelection = (\n current: SelectedFilter[],\n selected: SelectedFilter,\n filterDefinitions: Filter[]\n): SelectedFilter[] => {\n const alreadySelected = current.some((f) => f.name === selected.name && f.value === selected.value)\n if (alreadySelected) return current\n\n const supportsMulti = filterDefinitions.find((f) => f.filterName === selected.name)?.supportsMultiValue\n return supportsMulti ? [...current, selected] : [...current.filter((f) => f.name !== selected.name), selected]\n}\n\n/**\n * Builds URL search params object from filter settings for navigation\n */\nexport const buildUrlSearchParams = (\n selectedFilters: SelectedFilter[],\n filterDefinitions: Filter[],\n baseParams: {\n search?: string\n sortBy?: string\n sortDirection?: \"asc\" | \"desc\"\n }\n): SecurityGroupsSearchParams => {\n const newParams: SecurityGroupsSearchParams = {\n search: baseParams.search,\n sortBy: baseParams.sortBy,\n sortDirection: baseParams.sortDirection,\n }\n\n if (!selectedFilters?.length) return newParams\n\n const filterGroups = selectedFilters\n .filter((sf) => !sf.inactive)\n .reduce(\n (acc, sf) => {\n if (!acc[sf.name]) acc[sf.name] = []\n acc[sf.name].push(sf.value)\n return acc\n },\n {} as Record<string, string[]>\n )\n\n Object.entries(filterGroups).forEach(([filterName, values]) => {\n const filterDef = filterDefinitions.find((f) => f.filterName === filterName)\n\n if (filterDef?.supportsMultiValue && values.length > 1) {\n // Type assertion needed because we're dynamically setting filter fields\n ;(newParams as Record<string, string>)[filterName] = `in:${values.join(\",\")}`\n } else {\n ;(newParams as Record<string, string>)[filterName] = values[0]\n }\n })\n\n return newParams\n}\n","import { useState, useEffect } from \"react\"\nimport { Trans, useLingui } from \"@lingui/react/macro\"\nimport { useNavigate, useSearch } from \"@tanstack/react-router\"\nimport { Button, Stack, DataGridToolbar, SearchInput } from \"@cloudoperators/juno-ui-components\"\nimport { trpcReact } from \"@/client/trpcClient\"\nimport { SortInput } from \"@/client/components/ListToolbar/SortInput\"\nimport { SelectedFilters } from \"@/client/components/ListToolbar/SelectedFilters\"\nimport { FiltersInput } from \"@/client/components/ListToolbar/FiltersInput\"\nimport { FilterSettings, SortSettings } from \"@/client/components/ListToolbar/types\"\nimport { SecurityGroupListContainer } from \"./SecurityGroupListContainer\"\nimport { CreateSecurityGroupModal } from \"./-modals/CreateSecurityGroupModal\"\nimport { CreateSecurityGroupInput, UpdateSecurityGroupInput } from \"@/server/Network/types/securityGroup\"\nimport { parseFiltersFromUrl, buildFilterParams, buildUrlSearchParams, applyFilterSelection } from \"../urlHelpers\"\nimport { useSecurityGroupPermissions } from \"../-hooks/useSecurityGroupPermissions\"\n\ntype SecurityGroupSortKey = \"name\" | \"project_id\"\n\ntype SecurityGroupsSearchParams = {\n shared?: string\n search?: string\n sortBy?: string\n sortDirection?: \"asc\" | \"desc\"\n}\n\ntype RequiredSortSettings = {\n options: SortSettings[\"options\"]\n sortBy: string\n sortDirection: \"asc\" | \"desc\"\n}\n\ninterface SecurityGroupsProps {\n project: string\n}\n\nexport const SecurityGroups = ({ project: projectId }: SecurityGroupsProps) => {\n const { t } = useLingui()\n const navigate = useNavigate()\n const searchParams = useSearch({ strict: false }) as SecurityGroupsSearchParams\n\n const [sortSettings, setSortSettings] = useState<RequiredSortSettings>({\n options: [\n { label: t`Name`, value: \"name\" },\n { label: t`Project id`, value: \"project_id\" },\n ],\n sortBy: searchParams.sortBy || \"name\",\n sortDirection: searchParams.sortDirection || \"asc\",\n })\n\n const [filterSettings, setFilterSettings] = useState<FilterSettings>({\n filters: [\n {\n displayName: t`Shared`,\n filterName: \"shared\",\n values: [\"true\", \"false\"],\n supportsMultiValue: false,\n },\n ],\n selectedFilters: parseFiltersFromUrl(searchParams),\n })\n\n const [searchTerm, setSearchTerm] = useState(searchParams.search || \"\")\n const [createModalOpen, setCreateModalOpen] = useState(false)\n const [deleteError, setDeleteError] = useState<string | null>(null)\n const [createError, setCreateError] = useState<string | null>(null)\n const [updateError, setUpdateError] = useState<string | null>(null)\n\n const utils = trpcReact.useUtils()\n\n useEffect(() => {\n setSortSettings((prev) => ({\n ...prev,\n sortBy: searchParams.sortBy || \"name\",\n sortDirection: searchParams.sortDirection || \"asc\",\n }))\n setFilterSettings((prev) => ({\n ...prev,\n selectedFilters: parseFiltersFromUrl(searchParams),\n }))\n setSearchTerm(searchParams.search || \"\")\n }, [searchParams.sortBy, searchParams.sortDirection, searchParams.search, searchParams.shared])\n\n const urlFilters = parseFiltersFromUrl(searchParams)\n const urlSortBy = (searchParams.sortBy || \"name\") as SecurityGroupSortKey\n const urlSortDirection = searchParams.sortDirection || \"asc\"\n const urlSearchTerm = searchParams.search || \"\"\n\n const {\n data: securityGroupsData,\n isLoading,\n isError,\n error,\n } = trpcReact.network.securityGroup.list.useQuery(\n {\n project_id: projectId || \"\",\n sort_key: urlSortBy,\n sort_dir: urlSortDirection,\n ...buildFilterParams(urlFilters, filterSettings.filters),\n ...(urlSearchTerm ? { searchTerm: urlSearchTerm } : {}),\n },\n {\n refetchOnWindowFocus: false,\n }\n )\n\n const securityGroups = securityGroupsData || []\n const listError =\n isError && error?.data?.code === \"FORBIDDEN\" ? t`You do not have permission to view security groups` : null\n\n const { permissions } = useSecurityGroupPermissions(projectId)\n\n const createSecurityGroupMutation = trpcReact.network.securityGroup.create.useMutation({\n onSuccess: (createdSecurityGroup) => {\n utils.network.securityGroup.list.invalidate()\n utils.network.securityGroup.getById.invalidate()\n setCreateError(null)\n navigate({\n to: \"/projects/$projectId/network/securitygroups/$securityGroupId\",\n params: {\n projectId,\n securityGroupId: createdSecurityGroup.id,\n },\n })\n },\n onError: (error) => {\n setCreateError(error.message || t`Failed to create security group`)\n },\n })\n\n const deleteSecurityGroupMutation = trpcReact.network.securityGroup.deleteById.useMutation({\n onSuccess: () => {\n utils.network.securityGroup.list.invalidate()\n utils.network.securityGroup.getById.invalidate()\n setDeleteError(null)\n },\n onError: (error) => {\n setDeleteError(error.message || t`Failed to delete security group`)\n },\n })\n\n const updateSecurityGroupMutation = trpcReact.network.securityGroup.update.useMutation({\n onSuccess: () => {\n utils.network.securityGroup.list.invalidate()\n utils.network.securityGroup.getById.invalidate()\n setUpdateError(null)\n },\n onError: (error) => {\n setUpdateError(error.message || t`Failed to update security group`)\n },\n })\n\n const handleCreateSecurityGroup = async (securityGroupData: Omit<CreateSecurityGroupInput, \"project_id\">) => {\n setCreateError(null)\n await createSecurityGroupMutation.mutateAsync({ project_id: projectId, ...securityGroupData })\n }\n\n const handleDeleteSecurityGroup = (securityGroupId: string) => {\n setDeleteError(null)\n deleteSecurityGroupMutation.mutate({ project_id: projectId, securityGroupId })\n }\n\n const handleUpdateSecurityGroup = async (\n securityGroupId: string,\n data: Omit<UpdateSecurityGroupInput, \"securityGroupId\" | \"project_id\">\n ) => {\n setUpdateError(null)\n await updateSecurityGroupMutation.mutateAsync({ project_id: projectId, securityGroupId, ...data })\n }\n\n const handleClearUpdateError = () => {\n setUpdateError(null)\n }\n\n const handleSortChange = (newSortSettings: SortSettings) => {\n const settings: RequiredSortSettings = {\n options: newSortSettings.options,\n sortBy: newSortSettings.sortBy?.toString() || \"name\",\n sortDirection: newSortSettings.sortDirection || \"asc\",\n }\n setSortSettings(settings)\n navigate({\n search: ((prev: SecurityGroupsSearchParams) => ({\n ...prev,\n sortBy: settings.sortBy,\n sortDirection: settings.sortDirection,\n })) as unknown as true,\n replace: true,\n })\n }\n\n const handleFilterChange = (newFilterSettings: FilterSettings) => {\n setFilterSettings(newFilterSettings)\n navigate({\n search: ((prev: SecurityGroupsSearchParams) =>\n buildUrlSearchParams(newFilterSettings.selectedFilters || [], newFilterSettings.filters, {\n search: prev.search,\n sortBy: prev.sortBy,\n sortDirection: prev.sortDirection,\n })) as unknown as true,\n replace: true,\n })\n }\n\n const handleSearchChange = (term: string | number | string[] | undefined) => {\n const searchValue = typeof term === \"string\" ? term : \"\"\n setSearchTerm(searchValue)\n navigate({\n search: ((prev: SecurityGroupsSearchParams) => ({\n ...prev,\n search: searchValue || undefined,\n })) as unknown as true,\n replace: true,\n })\n }\n\n if (isLoading) {\n return (\n <Stack className=\"py-8\" distribution=\"center\" alignment=\"center\" direction=\"vertical\">\n <Trans>Loading...</Trans>\n </Stack>\n )\n }\n\n if (isError && !securityGroups.length) {\n return (\n <Stack className=\"py-8\" distribution=\"center\" alignment=\"center\" direction=\"vertical\">\n {listError || t`Failed to load security groups`}\n </Stack>\n )\n }\n\n return (\n <>\n <Stack distribution=\"end\" alignment=\"center\" gap=\"2\" className=\"pb-2\">\n <Stack gap=\"2\">\n <SortInput\n options={sortSettings.options}\n sortBy={sortSettings.sortBy}\n sortDirection={sortSettings.sortDirection ?? \"asc\"}\n onSortByChange={(v) =>\n handleSortChange({ ...sortSettings, sortBy: v, sortDirection: sortSettings.sortDirection })\n }\n onSortDirectionChange={(dir) => handleSortChange({ ...sortSettings, sortDirection: dir })}\n />\n {permissions.canCreate && (\n <Button onClick={() => setCreateModalOpen(true)} variant=\"primary\" className=\"whitespace-nowrap\">\n <Trans>Create Security Group</Trans>\n </Button>\n )}\n </Stack>\n </Stack>\n\n <DataGridToolbar>\n <Stack direction=\"vertical\" gap=\"2\">\n <Stack distribution=\"between\" alignment=\"center\">\n <FiltersInput\n filters={filterSettings.filters}\n onChange={(selected) => {\n const newSelected = applyFilterSelection(\n filterSettings.selectedFilters || [],\n selected,\n filterSettings.filters\n )\n if (newSelected === (filterSettings.selectedFilters || [])) return\n handleFilterChange({ ...filterSettings, selectedFilters: newSelected })\n }}\n />\n <SearchInput\n placeholder={t`Search security groups...`}\n data-testid=\"searchbar\"\n value={searchTerm}\n onInput={(e: React.FormEvent<HTMLInputElement>) => {\n const v = e.currentTarget.value\n setSearchTerm(v)\n }}\n onSearch={(v) => {\n handleSearchChange(typeof v === \"string\" ? v : \"\")\n }}\n onClear={() => {\n setSearchTerm(\"\")\n handleSearchChange(\"\")\n }}\n />\n </Stack>\n {filterSettings.selectedFilters && filterSettings.selectedFilters.length > 0 && (\n <SelectedFilters\n selectedFilters={filterSettings.selectedFilters}\n onDelete={(filterToRemove) =>\n handleFilterChange({\n ...filterSettings,\n selectedFilters: (filterSettings.selectedFilters || []).filter(\n (f) => !(f.name === filterToRemove.name && f.value === filterToRemove.value)\n ),\n })\n }\n onClear={() => handleFilterChange({ ...filterSettings, selectedFilters: [] })}\n />\n )}\n </Stack>\n </DataGridToolbar>\n\n <SecurityGroupListContainer\n securityGroups={securityGroups}\n isLoading={false}\n isError={false}\n error={null}\n permissions={permissions}\n onCreateClick={() => setCreateModalOpen(true)}\n onDeleteSecurityGroup={handleDeleteSecurityGroup}\n isDeletingSecurityGroup={deleteSecurityGroupMutation.isPending}\n deleteError={deleteError}\n onUpdateSecurityGroup={handleUpdateSecurityGroup}\n isUpdatingSecurityGroup={updateSecurityGroupMutation.isPending}\n updateError={updateError}\n currentProjectId={projectId}\n hasAnyBulkAction={false}\n onClearUpdateError={handleClearUpdateError}\n />\n\n <CreateSecurityGroupModal\n isOpen={createModalOpen}\n onClose={() => {\n setCreateError(null)\n setCreateModalOpen(false)\n }}\n onCreate={handleCreateSecurityGroup}\n isLoading={createSecurityGroupMutation.isPending}\n error={createError}\n />\n </>\n )\n}\n","import { createFileRoute } from \"@tanstack/react-router\"\nimport { t } from \"@lingui/core/macro\"\nimport { useLingui } from \"@lingui/react/macro\"\nimport { SecurityGroups } from \"./-components/SecurityGroupsList\"\nimport type { RouteInfo } from \"@/client/routes/routeInfo\"\nimport { ContentHeading } from \"@cloudoperators/juno-ui-components\"\n\nexport const Route = createFileRoute(\"/_auth/projects/$projectId/network/securitygroups/\")({\n staticData: {\n section: \"network\",\n service: \"securitygroups\",\n analytics: {\n name: \"network.securitygroups.list\",\n },\n sectionCrumb: { labelKey: \"Network\" },\n crumb: { labelKey: \"Security Groups\" },\n } satisfies RouteInfo,\n head: () => ({ meta: [{ title: t`Security Groups` }] }),\n component: RouteComponent,\n})\n\nfunction RouteComponent() {\n const { t } = useLingui()\n const { projectId } = Route.useParams()\n const { trpcClient } = Route.useRouteContext()\n\n if (!trpcClient) {\n throw new Error(\"trpcClient is not available in route context\")\n }\n\n return (\n <>\n <ContentHeading>{t`Security Groups`}</ContentHeading>\n <SecurityGroups project={projectId} />\n </>\n )\n}\n"],"mappings":"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"}
@@ -0,0 +1,71 @@
1
+ import { createTRPCReact as e } from "@trpc/react-query";
2
+ import { createTRPCClient as t, httpBatchLink as n, httpBatchStreamLink as r, httpLink as i, httpSubscriptionLink as a, isNonJsonSerializable as o, splitLink as s } from "@trpc/client";
3
+ //#region src/client/trpcClient.ts
4
+ var c = { bffEndpoint: "/polaris-bff" };
5
+ function l(e) {
6
+ c.bffEndpoint = e;
7
+ }
8
+ function u() {
9
+ return c.bffEndpoint;
10
+ }
11
+ var d = "/csrf-token", f = () => {
12
+ let e = /^https?:\/\//i.test(c.bffEndpoint) ? new URL(c.bffEndpoint).origin : globalThis.location.origin;
13
+ return new URL(d, e).href;
14
+ }, p = {
15
+ token: null,
16
+ pending: null,
17
+ async getHeaders() {
18
+ if (this.token) return { "x-csrf-token": this.token };
19
+ this.pending ||= fetch(f()).then((e) => e.json()).then(({ csrfToken: e }) => (this.token = e, this.pending = null, e)).catch((e) => (this.pending = null, console.error("Failed to fetch CSRF token:", e), null));
20
+ let e = await this.pending;
21
+ return e ? { "x-csrf-token": e } : {};
22
+ },
23
+ invalidate() {
24
+ this.token = null;
25
+ }
26
+ }, m = () => p.token, h = new Set(["storage.swift.downloadObject", "storage.ceph.objects.downloadObject"]), g = () => [s({
27
+ condition: (e) => e.type === "subscription",
28
+ true: a({
29
+ url: c.bffEndpoint,
30
+ eventSourceOptions: async () => ({ headers: await p.getHeaders() })
31
+ }),
32
+ false: s({
33
+ condition: (e) => o(e.input),
34
+ true: i({
35
+ url: c.bffEndpoint,
36
+ async headers({ op: e }) {
37
+ let t = await p.getHeaders(), n = e.context?.headers ?? {};
38
+ return {
39
+ ...t,
40
+ ...n
41
+ };
42
+ }
43
+ }),
44
+ false: s({
45
+ condition: (e) => h.has(e.path),
46
+ true: r({
47
+ url: c.bffEndpoint,
48
+ async headers() {
49
+ return p.getHeaders();
50
+ }
51
+ }),
52
+ false: n({
53
+ url: c.bffEndpoint,
54
+ async headers() {
55
+ return p.getHeaders();
56
+ }
57
+ })
58
+ })
59
+ })
60
+ })], _ = e(), v = {
61
+ react: null,
62
+ vanilla: null
63
+ }, y = new Proxy({}, { get(e, t) {
64
+ return v.react ||= _.createClient({ links: g() }), v.react[t];
65
+ } }), b = new Proxy({}, { get(e, n) {
66
+ return v.vanilla ||= t({ links: g() }), v.vanilla[n];
67
+ } });
68
+ //#endregion
69
+ export { _ as a, b as i, m as n, y as o, l as r, u as t };
70
+
71
+ //# sourceMappingURL=trpcClient-Di56hpVv.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trpcClient-Di56hpVv.mjs","names":["createTRPCReact","createTRPCClient","httpBatchLink","httpBatchStreamLink","httpLink","splitLink","isNonJsonSerializable","httpSubscriptionLink","config","bffEndpoint","setBffEndpoint","endpoint","getBffEndpoint","CSRF_TOKEN_PATH","csrfTokenUrl","base","test","URL","origin","globalThis","location","href","csrfCache","token","pending","getHeaders","fetch","then","res","json","csrfToken","catch","error","console","invalidate","getCsrfToken","setCsrfToken","STREAMING_PROCEDURES","Set","getLinks","condition","op","type","true","url","eventSourceOptions","headers","false","input","csrf","extra","context","has","path","trpcReact","clients","react","vanilla","trpcReactClient","Proxy","get","_target","prop","createClient","links","trpcClient"],"sources":["../../src/client/trpcClient.ts"],"sourcesContent":["import { createTRPCReact } from \"@trpc/react-query\"\nimport type { CreateTRPCReact } from \"@trpc/react-query\"\nimport { createTRPCClient, type TRPCClient } from \"@trpc/client\"\nimport {\n httpBatchLink,\n httpBatchStreamLink,\n httpLink,\n splitLink,\n isNonJsonSerializable,\n httpSubscriptionLink,\n} from \"@trpc/client\"\nimport type { AuroraRouter } from \"../server/routers\"\n\n/** Type alias for the React tRPC client */\nexport type TrpcReact = ReturnType<typeof createTRPCReact<AuroraRouter>>\n\n/** Type alias for the vanilla tRPC client */\nexport type TrpcClient = ReturnType<typeof createTRPCClient<AuroraRouter>>\n\n/** Generic type for creating a typed React tRPC client with a custom router */\nexport type CreateTypedTrpcReact<TRouter extends AuroraRouter> = CreateTRPCReact<TRouter, unknown>\n\n/** Generic type for creating a typed vanilla tRPC client with a custom router */\nexport type CreateTypedTrpcClient<TRouter extends AuroraRouter> = TRPCClient<TRouter>\n\n// BFF endpoint - set once by App before any tRPC calls are made\nconst config = {\n bffEndpoint: \"/polaris-bff\",\n}\n\nexport function setBffEndpoint(endpoint: string) {\n config.bffEndpoint = endpoint\n}\n\n/**\n * Read the endpoint App configured. Needed by code that has to hand the value to\n * a separate JS context — a Web Worker gets its own instance of this module and\n * never sees App's setBffEndpoint() call, so it would otherwise fall back to the\n * default and break any non-default deployment. Read-only: does not affect link\n * routing.\n */\nexport function getBffEndpoint() {\n return config.bffEndpoint\n}\n\n// The CSRF token route is mounted at the server root by AuroraFastifyCsrfProtection,\n// not under the BFF prefix.\nconst CSRF_TOKEN_PATH = \"/csrf-token\"\n\n// Origin to resolve CSRF_TOKEN_PATH against.\n//\n// A bare fetch(\"/csrf-token\") is fine on the main thread but throws inside the\n// object-download worker: it is inlined (?worker&inline) and therefore runs from\n// a blob: URL, whose location has an opaque path — a root-relative URL has\n// nothing to resolve against (\"Failed to parse URL\"). getHeaders()'s catch\n// swallowed that throw and returned {}, so the request went out with no\n// x-csrf-token and the BFF answered \"Invalid csrf token\".\n//\n// The worker is handed an already-absolute bffEndpoint (see objectDownloadStore),\n// so its origin is a base that holds in either context. A relative endpoint means\n// we're on the main thread, where location is a real document URL.\nconst csrfTokenUrl = () => {\n const base = /^https?:\\/\\//i.test(config.bffEndpoint)\n ? new URL(config.bffEndpoint).origin\n : globalThis.location.origin\n return new URL(CSRF_TOKEN_PATH, base).href\n}\n\n// CSRF token cache with request deduplication\nconst csrfCache = {\n token: null as string | null,\n pending: null as Promise<string | null> | null,\n\n async getHeaders() {\n if (this.token) {\n return { \"x-csrf-token\": this.token }\n }\n\n // Dedupe concurrent fetches - only one request in flight at a time\n if (!this.pending) {\n this.pending = fetch(csrfTokenUrl())\n .then((res) => res.json())\n .then(({ csrfToken }) => {\n this.token = csrfToken\n this.pending = null\n return csrfToken as string\n })\n .catch((error) => {\n this.pending = null\n console.error(\"Failed to fetch CSRF token:\", error)\n return null\n })\n }\n\n const token = await this.pending\n return token ? { \"x-csrf-token\": token } : {}\n },\n\n invalidate() {\n this.token = null\n },\n}\n\n/**\n * Read the cached CSRF token so it can be handed to a separate JS context.\n * Synchronous by design — callers that hand it to a worker are on a sync path.\n * Null means nothing has needed a token yet, in which case the other context\n * fetches one itself. Read-only.\n */\nexport const getCsrfToken = () => csrfCache.token\n\n/**\n * Seed the CSRF token from another JS context. Mirrors setBffEndpoint(): a Web\n * Worker gets its own instance of this module, so its cache starts empty and it\n * would otherwise spend a round-trip fetching a token the main thread already has.\n * A null token is ignored — the cache then resolves one on first use.\n */\nexport const setCsrfToken = (token: string | null) => {\n if (token) csrfCache.token = token\n}\n\n// Procedures that return async iterables (chunked streaming responses).\n// These are routed through httpBatchStreamLink instead of httpBatchLink.\n// Add any new streaming procedure paths here.\nconst STREAMING_PROCEDURES = new Set<string>([\"storage.swift.downloadObject\", \"storage.ceph.objects.downloadObject\"])\n\nconst getLinks = () => [\n splitLink({\n // First check: is it a subscription?\n condition: (op) => op.type === \"subscription\",\n // Use HTTP subscription link for subscriptions (long-polling)\n true: httpSubscriptionLink({\n url: config.bffEndpoint,\n // Callback to populate headers\n eventSourceOptions: async () => {\n return { headers: await csrfCache.getHeaders() }\n },\n }),\n // For non-subscriptions, check for non-JSON data or streaming procedures\n false: splitLink({\n // Non-JSON serializable input (FormData, Blob, ArrayBuffer, etc.) → plain httpLink.\n condition: (op) => isNonJsonSerializable(op.input),\n true: httpLink({\n url: config.bffEndpoint,\n async headers({ op }) {\n const csrf = await csrfCache.getHeaders()\n // Per-request headers can be injected via tRPC operation context.\n // op.context is set by the caller and forwarded through the link chain.\n const extra = (op.context as { headers?: Record<string, string> } | undefined)?.headers ?? {}\n return { ...csrf, ...extra }\n },\n }),\n // For JSON procedures, decide between streaming and regular batching\n false: splitLink({\n // Procedures returning async iterables must use httpBatchStreamLink.\n // httpBatchLink buffers the full response before resolving, which\n // breaks streaming. httpBatchStreamLink preserves batching while\n // supporting chunked/streamed responses.\n // Note: httpBatchStreamLink is intentionally NOT used globally because\n // it is incompatible with the @fastify/csrf-protection cookie rotation\n // used in this app — CSRF tokens can expire mid-stream on long-lived\n // connections. Scope it only to procedures that actually need streaming.\n condition: (op) => STREAMING_PROCEDURES.has(op.path),\n true: httpBatchStreamLink({\n url: config.bffEndpoint,\n async headers() {\n return csrfCache.getHeaders()\n },\n }),\n // Regular JSON mutations/queries — standard batching\n false: httpBatchLink({\n url: config.bffEndpoint,\n async headers() {\n return csrfCache.getHeaders()\n },\n }),\n }),\n }),\n }),\n]\n\n// React Query client (for hooks like useQuery/useMutation/useSubscription)\nexport const trpcReact: TrpcReact = createTRPCReact<AuroraRouter>()\n\n// Lazy-initialized clients - created on first access after setBffEndpoint() has been called by App\nconst clients = {\n react: null as ReturnType<typeof trpcReact.createClient> | null,\n vanilla: null as TrpcClient | null,\n}\n\nexport const trpcReactClient = new Proxy({} as ReturnType<typeof trpcReact.createClient>, {\n get(_target, prop) {\n if (!clients.react) {\n clients.react = trpcReact.createClient({ links: getLinks() })\n }\n return (clients.react as Record<string | symbol, unknown>)[prop]\n },\n})\n\nexport const trpcClient: TrpcClient = new Proxy({} as TrpcClient, {\n get(_target, prop) {\n if (!clients.vanilla) {\n clients.vanilla = createTRPCClient<AuroraRouter>({ links: getLinks() })\n }\n return (clients.vanilla as Record<string | symbol, unknown>)[prop]\n },\n})\n"],"mappings":";;;AA0BA,IAAMQ,IAAS,EACbC,aAAa,eACf;AAEA,SAAgBC,EAAeC,GAAgB;CAC7CH,EAAOC,cAAcE;AACvB;AASA,SAAgBC,IAAAA;CACd,OAAOJ,EAAOC;AAChB;AAIA,IAAMI,IAAkB,eAclBC,UAAe;CACnB,IAAMC,IAAO,gBAAgBC,KAAKR,EAAOC,WAAW,IAChD,IAAIQ,IAAIT,EAAOC,WAAW,EAAES,SAC5BC,WAAWC,SAASF;CACxB,OAAO,IAAID,IAAIJ,GAAiBE,CAAAA,EAAMM;AACxC,GAGMC,IAAY;CAChBC,OAAO;CACPC,SAAS;CAET,MAAMC,aAAAA;EACJ,IAAI,KAAKF,OACP,OAAO,EAAE,gBAAgB,KAAKA,MAAM;EAItC,AACE,KAAKC,YAAUE,MAAMZ,EAAAA,CAAAA,EAClBa,MAAMC,MAAQA,EAAIC,KAAI,CAAA,EACtBF,MAAM,EAAEG,oBACP,KAAKP,QAAQO,GACb,KAAKN,UAAU,MACRM,EACT,EACCC,OAAOC,OACN,KAAKR,UAAU,MACfS,QAAQD,MAAM,+BAA+BA,CAAAA,GACtC,KACT;EAGJ,IAAMT,IAAQ,MAAM,KAAKC;EACzB,OAAOD,IAAQ,EAAE,gBAAgBA,EAAM,IAAI,CAAC;CAC9C;CAEAW,aAAAA;EACE,KAAKX,QAAQ;CACf;AACF,GAQaY,UAAqBb,EAAUC,OAetCc,IAAuB,IAAIC,IAAY,CAAC,gCAAgC,qCAAA,CAAsC,GAE9GC,UAAiB,CACrBlC,EAAU;CAERmC,YAAYC,MAAOA,EAAGC,SAAS;CAE/BC,MAAMpC,EAAqB;EACzBqC,KAAKpC,EAAOC;EAEZoC,oBAAoB,aACX,EAAEC,SAAS,MAAMxB,EAAUG,WAAU,EAAG;CAEnD,CAAA;CAEAsB,OAAO1C,EAAU;EAEfmC,YAAYC,MAAOnC,EAAsBmC,EAAGO,KAAK;EACjDL,MAAMvC,EAAS;GACbwC,KAAKpC,EAAOC;GACZ,MAAMqC,QAAQ,EAAEL,SAAI;IAClB,IAAMQ,IAAO,MAAM3B,EAAUG,WAAU,GAGjCyB,IAAQ,EAAIC,SAA8DL,WAAW,CAAC;IAC5F,OAAO;KAAE,GAAGG;KAAM,GAAGC;IAAM;GAC7B;EACF,CAAA;EAEAH,OAAO1C,EAAU;GASfmC,YAAYC,MAAOJ,EAAqBe,IAAIX,EAAGY,IAAI;GACnDV,MAAMxC,EAAoB;IACxByC,KAAKpC,EAAOC;IACZ,MAAMqC,UAAAA;KACJ,OAAOxB,EAAUG,WAAU;IAC7B;GACF,CAAA;GAEAsB,OAAO7C,EAAc;IACnB0C,KAAKpC,EAAOC;IACZ,MAAMqC,UAAAA;KACJ,OAAOxB,EAAUG,WAAU;IAC7B;GACF,CAAA;EACF,CAAA;CACF,CAAA;AACF,CAAA,CAAA,GAIW6B,IAAuBtD,EAAAA,GAG9BuD,IAAU;CACdC,OAAO;CACPC,SAAS;AACX,GAEaC,IAAkB,IAAIC,MAAM,CAAC,GAAgD,EACxFC,IAAIC,GAASC,GAAI;CAIf,OAHA,AACEP,EAAQC,UAAQF,EAAUS,aAAa,EAAEC,OAAOzB,EAAAA,EAAW,CAAA,GAEtD,EAASiB,MAA2CM;AAC7D,EACF,CAAA,GAEaG,IAAyB,IAAIN,MAAM,CAAC,GAAiB,EAChEC,IAAIC,GAASC,GAAI;CAIf,OAHA,AACEP,EAAQE,YAAUxD,EAA+B,EAAE+D,OAAOzB,EAAAA,EAAW,CAAA,GAEhE,EAASkB,QAA6CK;AAC/D,EACF,CAAA"}
@@ -9,12 +9,29 @@ export type TrpcClient = ReturnType<typeof createTRPCClient<AuroraRouter>>;
9
9
  export type CreateTypedTrpcReact<TRouter extends AuroraRouter> = CreateTRPCReact<TRouter, unknown>;
10
10
  /** Generic type for creating a typed vanilla tRPC client with a custom router */
11
11
  export type CreateTypedTrpcClient<TRouter extends AuroraRouter> = TRPCClient<TRouter>;
12
+ export declare function setBffEndpoint(endpoint: string): void;
12
13
  /**
13
- * Invalidate the cached CSRF token.
14
- * Call this when receiving a 403 response to trigger a fresh token fetch on the next request.
14
+ * Read the endpoint App configured. Needed by code that has to hand the value to
15
+ * a separate JS context — a Web Worker gets its own instance of this module and
16
+ * never sees App's setBffEndpoint() call, so it would otherwise fall back to the
17
+ * default and break any non-default deployment. Read-only: does not affect link
18
+ * routing.
15
19
  */
16
- export declare const invalidateCsrfToken: () => void;
17
- export declare function setBffEndpoint(endpoint: string): void;
20
+ export declare function getBffEndpoint(): string;
21
+ /**
22
+ * Read the cached CSRF token so it can be handed to a separate JS context.
23
+ * Synchronous by design — callers that hand it to a worker are on a sync path.
24
+ * Null means nothing has needed a token yet, in which case the other context
25
+ * fetches one itself. Read-only.
26
+ */
27
+ export declare const getCsrfToken: () => string | null;
28
+ /**
29
+ * Seed the CSRF token from another JS context. Mirrors setBffEndpoint(): a Web
30
+ * Worker gets its own instance of this module, so its cache starts empty and it
31
+ * would otherwise spend a round-trip fetching a token the main thread already has.
32
+ * A null token is ignored — the cache then resolves one on first use.
33
+ */
34
+ export declare const setCsrfToken: (token: string | null) => void;
18
35
  export declare const trpcReact: TrpcReact;
19
36
  export declare const trpcReactClient: TRPCClient<import('@trpc/server').TRPCBuiltRouter<{
20
37
  ctx: import('../server/context').AuroraPortalContext;
@@ -1770,6 +1787,25 @@ export declare const trpcReactClient: TRPCClient<import('@trpc/server').TRPCBuil
1770
1787
  }, void, any>;
1771
1788
  meta: object;
1772
1789
  }>;
1790
+ uploadObject: import('@trpc/server').TRPCMutationProcedure<{
1791
+ input: import('@trpc/server/unstable-core-do-not-import').OctetInput;
1792
+ output: {
1793
+ success: boolean;
1794
+ };
1795
+ meta: object;
1796
+ }>;
1797
+ watchUploadProgress: import('@trpc/server').TRPCSubscriptionProcedure<{
1798
+ input: {
1799
+ project_id: string;
1800
+ uploadId: string;
1801
+ };
1802
+ output: AsyncIterable<{
1803
+ uploaded: number;
1804
+ total: number;
1805
+ percent: number;
1806
+ }, void, any>;
1807
+ meta: object;
1808
+ }>;
1773
1809
  }>>;
1774
1810
  versioning: import('@trpc/server').TRPCBuiltRouter<{
1775
1811
  ctx: import('../server/context').AuroraPortalContext;
@@ -0,0 +1,32 @@
1
+ import { startTransition as e, useState as t } from "react";
2
+ //#region src/client/utils/useListWithFiltering.ts
3
+ function n({ defaultSortKey: n, defaultSortDir: r, sortOptions: i, filterSettings: a }) {
4
+ let [o, s] = t(""), c = (t) => {
5
+ let n = typeof t == "string" ? t : "";
6
+ e(() => s(n));
7
+ }, [l, u] = t({
8
+ options: i,
9
+ sortBy: n,
10
+ sortDirection: r
11
+ }), d = (e) => {
12
+ u({
13
+ options: e.options ?? l.options,
14
+ sortBy: e.sortBy?.toString() || n,
15
+ sortDirection: e.sortDirection || r
16
+ });
17
+ }, [f, p] = t(a);
18
+ return {
19
+ searchTerm: o,
20
+ sortSettings: l,
21
+ filterSettings: f,
22
+ handleSearchChange: c,
23
+ handleSortChange: d,
24
+ handleFilterChange: (t) => {
25
+ e(() => p(t));
26
+ }
27
+ };
28
+ }
29
+ //#endregion
30
+ export { n as t };
31
+
32
+ //# sourceMappingURL=useListWithFiltering-aPTUNRaW.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useListWithFiltering-aPTUNRaW.mjs","names":["startTransition","useState","useListWithFiltering","defaultSortKey","defaultSortDir","sortOptions","filterSettings","initialFilterSettings","searchTerm","setSearchTerm","handleSearchChange","term","searchValue","sortSettings","setSortSettings","options","sortBy","sortDirection","handleSortChange","newSortSettings","settings","toString","setFilterSettings","handleFilterChange","newFilterSettings"],"sources":["../../src/client/utils/useListWithFiltering.ts"],"sourcesContent":["import { startTransition, useState } from \"react\"\nimport { FilterSettings, SortOption, SortSettings } from \"@/client/components/ListToolbar/types\"\n\n/**\n * Sort direction enumeration\n * Used across all sort-enabled list views\n */\nexport type SortDirection = \"asc\" | \"desc\"\n\n/**\n * Generic required sort settings with guaranteed non-optional properties\n * Used by components to maintain a fully-defined sort state\n *\n * Type parameter K is the sort key type (string literal union of allowed sort fields)\n *\n * @example\n * // For floating IPs\n * type FloatingIpListSortSettings = ListSortConfig<FloatingIpsSortKey>\n *\n * // For security groups\n * type SecurityGroupListSortSettings = ListSortConfig<\"name\" | \"project_id\">\n */\nexport type ListSortConfig<T extends string = string> = {\n /**\n * Array of available sort options to display in the UI\n */\n options: SortOption[]\n /**\n * The currently selected sort field (guaranteed to be one of the available options)\n */\n sortBy: T\n /**\n * The currently active sort direction (guaranteed to be either \"asc\" or \"desc\")\n */\n sortDirection: SortDirection\n}\n\n/**\n * Configuration options for the useListWithFiltering hook\n */\nexport interface UseListWithFilteringOptions<T extends string> {\n /** Default sort key to use on initial render */\n defaultSortKey: T\n /** Default sort direction to use on initial render */\n defaultSortDir: SortDirection\n /** Available sort options to display in the sort dropdown */\n sortOptions: Array<{ label: string; value: string }>\n /** Initial filter settings configuration */\n filterSettings: FilterSettings\n}\n\n/**\n * Return value from the useListWithFiltering hook\n */\nexport interface UseListWithFilteringReturn<T extends string> {\n // State\n searchTerm: string\n sortSettings: ListSortConfig<T>\n filterSettings: FilterSettings\n\n // Handlers\n handleSearchChange: (term: string | number | string[] | undefined) => void\n handleSortChange: (newSortSettings: SortSettings) => void\n handleFilterChange: (newFilterSettings: FilterSettings) => void\n}\n\n/**\n * Custom hook to manage common list view state: search, sort, and filter\n *\n * Extracts shared logic from list components to ensure consistent behavior\n * across all list pages and reduce code duplication.\n *\n * @example\n * ```tsx\n * const listState = useListWithFiltering({\n * defaultSortKey: \"name\",\n * defaultSortDir: \"asc\",\n * sortOptions: [\n * { label: t`Name`, value: \"name\" },\n * { label: t`Created`, value: \"created_at\" },\n * ],\n * filterSettings: {\n * filters: [\n * { displayName: t`Status`, filterName: \"status\", values: [\"active\", \"inactive\"] }\n * ]\n * }\n * })\n * ```\n */\nexport function useListWithFiltering<T extends string>({\n defaultSortKey,\n defaultSortDir,\n sortOptions,\n filterSettings: initialFilterSettings,\n}: UseListWithFilteringOptions<T>): UseListWithFilteringReturn<T> {\n // Search\n const [searchTerm, setSearchTerm] = useState(\"\")\n const handleSearchChange = (term: string | number | string[] | undefined) => {\n const searchValue = typeof term === \"string\" ? term : \"\"\n startTransition(() => setSearchTerm(searchValue))\n }\n\n // Sort\n const [sortSettings, setSortSettings] = useState<ListSortConfig<T>>({\n options: sortOptions,\n sortBy: defaultSortKey,\n sortDirection: defaultSortDir,\n })\n const handleSortChange = (newSortSettings: SortSettings) => {\n const settings: ListSortConfig<T> = {\n options: newSortSettings.options ?? sortSettings.options,\n sortBy: (newSortSettings.sortBy?.toString() as T) || defaultSortKey,\n sortDirection: (newSortSettings.sortDirection as SortDirection) || defaultSortDir,\n }\n setSortSettings(settings)\n }\n\n // Filter\n const [filterSettings, setFilterSettings] = useState<FilterSettings>(initialFilterSettings)\n const handleFilterChange = (newFilterSettings: FilterSettings) => {\n startTransition(() => setFilterSettings(newFilterSettings))\n }\n\n return {\n searchTerm,\n sortSettings,\n filterSettings,\n handleSearchChange,\n handleSortChange,\n handleFilterChange,\n }\n}\n"],"mappings":";;AAyFA,SAAgBE,EAAuC,EACrDC,mBACAC,mBACAC,gBACAC,gBAAgBC,KACe;CAE/B,IAAM,CAACC,GAAYC,KAAiBR,EAAS,EAAA,GACvCS,KAAsBC,MAAAA;EAC1B,IAAMC,IAAc,OAAOD,KAAS,WAAWA,IAAO;EACtDX,QAAsBS,EAAcG,CAAAA,CAAAA;CACtC,GAGM,CAACC,GAAcC,KAAmBb,EAA4B;EAClEc,SAASV;EACTW,QAAQb;EACRc,eAAeb;CACjB,CAAA,GACMc,KAAoBC,MAAAA;EAMxBL,EAAgBM;GAJdL,SAASI,EAAgBJ,WAAWF,EAAaE;GACjDC,QAAQ,EAAiBA,QAAQK,SAAAA,KAAoBlB;GACrDc,eAAe,EAAiBA,iBAAmCb;EAErDgB,CAAAA;CAClB,GAGM,CAACd,GAAgBgB,KAAqBrB,EAAyBM,CAAAA;CAKrE,OAAO;EACLC;EACAK;EACAP;EACAI;EACAQ;EACAK,qBAV0BC,MAAAA;GAC1BxB,QAAsBsB,EAAkBE,CAAAA,CAAAA;EAC1C;CASA;AACF"}
@@ -0,0 +1,161 @@
1
+ import { B as e, H as t, K as n, U as r, c as i, et as a, g as o, o as s, r as c, ut as l, y as u } from "./build-CRLQyqu2.mjs";
2
+ import { a as d } from "./trpcClient-Di56hpVv.mjs";
3
+ import { jsx as f, jsxs as p } from "react/jsx-runtime";
4
+ import { useEffect as m, useState as h } from "react";
5
+ import { Trans as g, useLingui as _ } from "@lingui/react";
6
+ //#region src/client/routes/_auth/projects/$projectId/network/securitygroups/-components/-modals/EditSecurityGroupModal.tsx
7
+ var v = ({ securityGroup: d, open: v, onClose: y, onUpdate: b, isLoading: x = !1, error: S = null }) => {
8
+ let { i18n: C, _: w } = _(), [T, E] = h({
9
+ name: d.name || "",
10
+ description: d.description || ""
11
+ }), [D, O] = h({});
12
+ m(() => {
13
+ E({
14
+ name: d.name || "",
15
+ description: d.description || ""
16
+ });
17
+ }, [d]);
18
+ let k = (e) => {
19
+ let { name: t, value: n, type: r } = e.target, i = e.target.checked;
20
+ E((e) => ({
21
+ ...e,
22
+ [t]: r === "checkbox" ? i : n
23
+ })), D[t] && O((e) => {
24
+ let n = { ...e };
25
+ return delete n[t], n;
26
+ });
27
+ }, A = () => {
28
+ let e = {};
29
+ return (!T.name || T.name.trim() === "") && (e.name = C._({ id: "lN/Z9n" })), O(e), Object.keys(e).length === 0;
30
+ }, j = async (e) => {
31
+ if (e.preventDefault(), A() && b) {
32
+ let e = {
33
+ name: T.name.trim(),
34
+ description: T.description.trim() || void 0
35
+ };
36
+ await b(d.id, e);
37
+ }
38
+ }, M = () => {
39
+ O({}), y();
40
+ };
41
+ return /*#__PURE__*/ p(c, {
42
+ open: v,
43
+ onCancel: M,
44
+ size: "large",
45
+ title: C._({ id: "yu9G3x" }),
46
+ modalFooter: /*#__PURE__*/ f(o, {
47
+ className: "flex justify-end",
48
+ children: /*#__PURE__*/ p(l, { children: [/*#__PURE__*/ f(u, {
49
+ variant: "default",
50
+ onClick: M,
51
+ disabled: x,
52
+ children: /*#__PURE__*/ f(g, { id: "dEgA5A" })
53
+ }), /*#__PURE__*/ f(u, {
54
+ variant: "primary",
55
+ onClick: (e) => {
56
+ j(e);
57
+ },
58
+ disabled: x,
59
+ "data-testid": "update-security-group-button",
60
+ children: x ? /*#__PURE__*/ f(n, { size: "small" }) : /*#__PURE__*/ f(g, { id: "Df0YHr" })
61
+ })] })
62
+ }),
63
+ children: [
64
+ S && /*#__PURE__*/ f(e, {
65
+ dismissible: !1,
66
+ variant: "error",
67
+ className: "mb-4",
68
+ children: S
69
+ }),
70
+ x && /*#__PURE__*/ p("div", {
71
+ className: "mb-4 flex items-center justify-center gap-2",
72
+ children: [/*#__PURE__*/ f(n, { variant: "primary" }), /*#__PURE__*/ f("span", {
73
+ className: "text-theme-high text-sm",
74
+ children: /*#__PURE__*/ f(g, { id: "V804LY" })
75
+ })]
76
+ }),
77
+ !x && /*#__PURE__*/ f(s, {
78
+ className: "mb-6",
79
+ children: /*#__PURE__*/ p(t, {
80
+ className: "mb-6",
81
+ children: [/*#__PURE__*/ f(i, {
82
+ className: "mb-6",
83
+ children: /*#__PURE__*/ f(r, {
84
+ id: "name",
85
+ name: "name",
86
+ label: C._({ id: "6YtxFj" }),
87
+ value: T.name,
88
+ onChange: k,
89
+ required: !0,
90
+ errortext: D.name,
91
+ placeholder: C._({ id: "Ac6dy9" }),
92
+ disabled: x
93
+ })
94
+ }), /*#__PURE__*/ f(i, {
95
+ className: "mb-6",
96
+ children: /*#__PURE__*/ f(a, {
97
+ id: "description",
98
+ name: "description",
99
+ label: C._({ id: "Nu4oKW" }),
100
+ value: T.description,
101
+ onChange: k,
102
+ placeholder: C._({ id: "Nu4oKW" }),
103
+ disabled: x,
104
+ rows: 3
105
+ })
106
+ })]
107
+ })
108
+ })
109
+ ]
110
+ });
111
+ };
112
+ //#endregion
113
+ //#region src/client/routes/_auth/projects/$projectId/network/securitygroups/-hooks/useSecurityGroupPermissions.ts
114
+ function y(e) {
115
+ let { data: t = {
116
+ canView: !1,
117
+ canCreate: !1,
118
+ canUpdate: !1,
119
+ canDelete: !1,
120
+ canCreateRule: !1,
121
+ canDeleteRule: !1,
122
+ canManageAccess: !1,
123
+ canViewRBAC: !1
124
+ }, isLoading: n, isError: r } = d.network.canUser.useQuery({
125
+ project_id: e || "",
126
+ permission: [
127
+ "network:security_groups:read",
128
+ "network:security_groups:create",
129
+ "network:security_groups:update",
130
+ "network:security_groups:delete",
131
+ "network:security_group_rules:create",
132
+ "network:security_group_rules:delete",
133
+ "network:rbac_policies:create",
134
+ "network:rbac_policies:delete",
135
+ "network:rbac_policies:read"
136
+ ]
137
+ }, {
138
+ enabled: !!e,
139
+ select: ([e, t, n, r, i, a, o, s, c]) => ({
140
+ canView: e,
141
+ canCreate: t,
142
+ canUpdate: n,
143
+ canDelete: r,
144
+ canCreateRule: i,
145
+ canDeleteRule: a,
146
+ canManageAccess: o && s,
147
+ canViewRBAC: c
148
+ }),
149
+ staleTime: Infinity,
150
+ gcTime: Infinity
151
+ });
152
+ return {
153
+ permissions: t,
154
+ isLoading: n,
155
+ isError: r
156
+ };
157
+ }
158
+ //#endregion
159
+ export { v as n, y as t };
160
+
161
+ //# sourceMappingURL=useSecurityGroupPermissions-BPS1ibWq.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSecurityGroupPermissions-BPS1ibWq.mjs","names":["React","useState","useEffect","Modal","Form","FormRow","FormSection","TextInput","Button","ButtonRow","Spinner","ModalFooter","Textarea","Message","EditSecurityGroupModal","securityGroup","open","onClose","onUpdate","isLoading","error","useLingui","properties","setProperties","name","description","errors","setErrors","handleInputChange","e","value","type","target","checked","prev","newErrors","validateForm","trim","t","Object","keys","length","handleSubmit","preventDefault","updateData","undefined","id","handleClose","onCancel","size","title","modalFooter","className","variant","onClick","disabled","data-testid","dismissible","div","span","label","onChange","required","errortext","placeholder","rows","trpcReact","useSecurityGroupPermissions","projectId","data","permissions","canView","canCreate","canUpdate","canDelete","canCreateRule","canDeleteRule","canManageAccess","canViewRBAC","isLoading","isError","network","canUser","useQuery","project_id","permission","enabled","Boolean","select","canCreateRBAC","canDeleteRBAC","staleTime","Infinity","gcTime"],"sources":["../../src/client/routes/_auth/projects/$projectId/network/securitygroups/-components/-modals/EditSecurityGroupModal.tsx","../../src/client/routes/_auth/projects/$projectId/network/securitygroups/-hooks/useSecurityGroupPermissions.ts"],"sourcesContent":["import React, { useState, useEffect } from \"react\"\nimport { Trans, useLingui } from \"@lingui/react/macro\"\nimport {\n Modal,\n Form,\n FormRow,\n FormSection,\n TextInput,\n Button,\n ButtonRow,\n Spinner,\n ModalFooter,\n Textarea,\n Message,\n} from \"@cloudoperators/juno-ui-components\"\nimport type { SecurityGroup } from \"@/server/Network/types/securityGroup\"\nimport { UpdateSecurityGroupInput } from \"@/server/Network/types/securityGroup\"\n\ninterface EditSecurityGroupModalProps {\n securityGroup: SecurityGroup\n open: boolean\n onClose: () => void\n onUpdate?: (\n securityGroupId: string,\n data: Omit<UpdateSecurityGroupInput, \"securityGroupId\" | \"project_id\">\n ) => Promise<void>\n isLoading?: boolean\n error?: string | null\n}\n\ninterface SecurityGroupProperties {\n name: string\n description: string\n}\n\nexport const EditSecurityGroupModal: React.FC<EditSecurityGroupModalProps> = ({\n securityGroup,\n open,\n onClose,\n onUpdate,\n isLoading = false,\n error = null,\n}) => {\n const { t } = useLingui()\n\n const [properties, setProperties] = useState<SecurityGroupProperties>({\n name: securityGroup.name || \"\",\n description: securityGroup.description || \"\",\n })\n const [errors, setErrors] = useState<{ [key: string]: string }>({})\n\n // Update properties when securityGroup changes\n useEffect(() => {\n setProperties({\n name: securityGroup.name || \"\",\n description: securityGroup.description || \"\",\n })\n }, [securityGroup])\n\n const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {\n const { name, value, type } = e.target\n const checked = (e.target as HTMLInputElement).checked\n\n setProperties((prev) => ({\n ...prev,\n [name]: type === \"checkbox\" ? checked : value,\n }))\n\n if (errors[name]) {\n setErrors((prev) => {\n const newErrors = { ...prev }\n delete newErrors[name]\n return newErrors\n })\n }\n }\n\n const validateForm = (): boolean => {\n const newErrors: { [key: string]: string } = {}\n\n if (!properties.name || properties.name.trim() === \"\") {\n newErrors.name = t`Security group name is required`\n }\n\n setErrors(newErrors)\n return Object.keys(newErrors).length === 0\n }\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault()\n\n if (!validateForm()) {\n return\n }\n\n if (onUpdate) {\n // Prepare base update data without stateful\n const updateData: Omit<UpdateSecurityGroupInput, \"securityGroupId\" | \"project_id\"> = {\n name: properties.name.trim(),\n description: properties.description.trim() || undefined,\n }\n\n // Note: We deliberately do NOT include 'stateful' field here\n // because it requires special 'update_security_group:stateful' permission\n // which needs cloud admin role. Regular users can only update name/description.\n\n await onUpdate(securityGroup.id, updateData)\n }\n }\n\n const handleClose = () => {\n setErrors({})\n onClose()\n }\n\n return (\n <Modal\n open={open}\n onCancel={handleClose}\n size=\"large\"\n title={t`Edit Security Group`}\n modalFooter={\n <ModalFooter className=\"flex justify-end\">\n <ButtonRow>\n <Button variant=\"default\" onClick={handleClose} disabled={isLoading}>\n <Trans>Cancel</Trans>\n </Button>\n <Button\n variant=\"primary\"\n onClick={(e) => {\n handleSubmit(e)\n }}\n disabled={isLoading}\n data-testid=\"update-security-group-button\"\n >\n {isLoading ? <Spinner size=\"small\" /> : <Trans>Update Security Group</Trans>}\n </Button>\n </ButtonRow>\n </ModalFooter>\n }\n >\n {/* Error Message */}\n {error && (\n <Message dismissible={false} variant=\"error\" className=\"mb-4\">\n {error}\n </Message>\n )}\n\n {isLoading && (\n <div className=\"mb-4 flex items-center justify-center gap-2\">\n <Spinner variant=\"primary\" />\n <span className=\"text-theme-high text-sm\">\n <Trans>Updating security group...</Trans>\n </span>\n </div>\n )}\n\n {!isLoading && (\n <Form className=\"mb-6\">\n <FormSection className=\"mb-6\">\n <FormRow className=\"mb-6\">\n <TextInput\n id=\"name\"\n name=\"name\"\n label={t`Name`}\n value={properties.name}\n onChange={handleInputChange}\n required\n errortext={errors.name}\n placeholder={t`Type name`}\n disabled={isLoading}\n />\n </FormRow>\n\n <FormRow className=\"mb-6\">\n <Textarea\n id=\"description\"\n name=\"description\"\n label={t`Description`}\n value={properties.description}\n onChange={handleInputChange}\n placeholder={t`Description`}\n disabled={isLoading}\n rows={3}\n />\n </FormRow>\n {/* Note: Stateful checkbox is not shown here because it requires 'update_security_group:stateful' permission (cloud admin only) */}\n </FormSection>\n </Form>\n )}\n </Modal>\n )\n}\n","import { trpcReact } from \"@/client/trpcClient\"\n\nexport interface SecurityGroupPermissions {\n canView: boolean\n canCreate: boolean\n canUpdate: boolean\n canDelete: boolean\n canCreateRule: boolean\n canDeleteRule: boolean\n canManageAccess: boolean\n canViewRBAC: boolean\n}\n\n/**\n * Hook to fetch security group permissions for the current user\n * Uses React Query with infinite cache since permissions don't change during session\n */\nexport function useSecurityGroupPermissions(projectId: string) {\n const {\n data: permissions = {\n canView: false,\n canCreate: false,\n canUpdate: false,\n canDelete: false,\n canCreateRule: false,\n canDeleteRule: false,\n canManageAccess: false,\n canViewRBAC: false,\n },\n isLoading,\n isError,\n } = trpcReact.network.canUser.useQuery(\n {\n project_id: projectId || \"\",\n permission: [\n \"network:security_groups:read\",\n \"network:security_groups:create\",\n \"network:security_groups:update\",\n \"network:security_groups:delete\",\n \"network:security_group_rules:create\",\n \"network:security_group_rules:delete\",\n \"network:rbac_policies:create\",\n \"network:rbac_policies:delete\",\n \"network:rbac_policies:read\",\n ],\n },\n {\n enabled: Boolean(projectId), // Only fetch if we have a project ID\n select: ([\n canView,\n canCreate,\n canUpdate,\n canDelete,\n canCreateRule,\n canDeleteRule,\n canCreateRBAC,\n canDeleteRBAC,\n canViewRBAC,\n ]) => ({\n canView,\n canCreate,\n canUpdate,\n canDelete,\n canCreateRule,\n canDeleteRule,\n canManageAccess: canCreateRBAC && canDeleteRBAC,\n canViewRBAC,\n }),\n staleTime: Infinity, // Permissions don't change during session\n gcTime: Infinity, // Keep in cache forever (previously cacheTime)\n }\n )\n\n return {\n permissions,\n isLoading,\n isError,\n }\n}\n"],"mappings":"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"}