@cobaltcore-dev/aurora 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/{EditSecurityGroupModal-Dl6m7wUe.mjs → EditSecurityGroupModal-DK3WYikA.mjs} +1 -1
- package/dist/client/{EditSecurityGroupModal-Dl6m7wUe.mjs.map → EditSecurityGroupModal-DK3WYikA.mjs.map} +1 -1
- package/dist/client/{FiltersInput-BgNaHFBt.mjs → FiltersInput-OVeIJzIo.mjs} +2 -2
- package/dist/client/FiltersInput-OVeIJzIo.mjs.map +1 -0
- package/dist/client/{FloatingIpActionModals-By2hXR9m.mjs → FloatingIpActionModals-Dok7fJss.mjs} +3 -3
- package/dist/client/{FloatingIpActionModals-By2hXR9m.mjs.map → FloatingIpActionModals-Dok7fJss.mjs.map} +1 -1
- package/dist/client/{ImageToastNotifications-fHI8jB2j.mjs → ImageToastNotifications-BG9LPnXf.mjs} +4 -4
- package/dist/client/{ImageToastNotifications-fHI8jB2j.mjs.map → ImageToastNotifications-BG9LPnXf.mjs.map} +1 -1
- package/dist/client/{ListToolbar-BvtCo8dk.mjs → ListToolbar-BojRTNbo.mjs} +2 -2
- package/dist/client/{ListToolbar-BvtCo8dk.mjs.map → ListToolbar-BojRTNbo.mjs.map} +1 -1
- package/dist/client/{_floatingIpId-CwHiXazi.mjs → _floatingIpId-D33bOEmH.mjs} +2 -2
- package/dist/client/{_floatingIpId-CwHiXazi.mjs.map → _floatingIpId-D33bOEmH.mjs.map} +1 -1
- package/dist/client/{_floatingIpId-BpUfL8Im2.mjs → _floatingIpId-DF_BSJN6.mjs} +5 -5
- package/dist/client/_floatingIpId-DF_BSJN6.mjs.map +1 -0
- package/dist/client/{_imageId-CdOOJjw0.mjs → _imageId-BL0I5_pv.mjs} +2 -2
- package/dist/client/{_imageId-CdOOJjw0.mjs.map → _imageId-BL0I5_pv.mjs.map} +1 -1
- package/dist/client/{_pcaId-D1ZEaCdp.mjs → _pcaId-BYCoeK6_.mjs} +2 -2
- package/dist/client/{_pcaId-D1ZEaCdp.mjs.map → _pcaId-BYCoeK6_.mjs.map} +1 -1
- package/dist/client/{_pcaId-CwlH1Kvl.mjs → _pcaId-CbBhBrX1.mjs} +153 -56
- package/dist/client/_pcaId-CbBhBrX1.mjs.map +1 -0
- package/dist/client/{_projectId-Dj_InfSc.mjs → _projectId-5NiasyXm.mjs} +2 -2
- package/dist/client/{_projectId-Dj_InfSc.mjs.map → _projectId-5NiasyXm.mjs.map} +1 -1
- package/dist/client/{_projectId-DhLpIalx.mjs → _projectId-BwLMEMGC.mjs} +1 -1
- package/dist/client/{_projectId-DhLpIalx.mjs.map → _projectId-BwLMEMGC.mjs.map} +1 -1
- package/dist/client/{_projectId-D1gGribM.mjs → _projectId-D35MN1kY.mjs} +3 -3
- package/dist/client/{_projectId-D1gGribM.mjs.map → _projectId-D35MN1kY.mjs.map} +1 -1
- package/dist/client/{_securityGroupId-B0llWH9A.mjs → _securityGroupId-B-Z-CzLp.mjs} +2 -2
- package/dist/client/{_securityGroupId-B0llWH9A.mjs.map → _securityGroupId-B-Z-CzLp.mjs.map} +1 -1
- package/dist/client/{_securityGroupId-gbUnd5Wv.mjs → _securityGroupId-B1bOYRbX.mjs} +7 -7
- package/dist/client/{_securityGroupId-gbUnd5Wv.mjs.map → _securityGroupId-B1bOYRbX.mjs.map} +1 -1
- package/dist/client/{about-DCe6LsKz.mjs → about-DLn1ShhF.mjs} +1 -1
- package/dist/client/{about-DCe6LsKz.mjs.map → about-DLn1ShhF.mjs.map} +1 -1
- package/dist/client/{buildFilterParams-By33pG59.mjs → buildFilterParams-TeyosGyK.mjs} +1 -1
- package/dist/client/{buildFilterParams-By33pG59.mjs.map → buildFilterParams-TeyosGyK.mjs.map} +1 -1
- package/dist/client/{constants-CAjjRTo_.mjs → constants-B-P2r5F1.mjs} +3 -3
- package/dist/client/{constants-CAjjRTo_.mjs.map → constants-B-P2r5F1.mjs.map} +1 -1
- package/dist/client/{containers-NW7RnHTI.mjs → containers-BjWqjNOx.mjs} +2 -2
- package/dist/client/containers-BjWqjNOx.mjs.map +1 -0
- package/dist/client/{containers-Ca5V1EBS.mjs → containers-DsRWc1L5.mjs} +1 -1
- package/dist/client/containers-DsRWc1L5.mjs.map +1 -0
- package/dist/client/{containers-BuXUVb1N.mjs → containers-J7WFA18U.mjs} +1140 -1051
- package/dist/client/containers-J7WFA18U.mjs.map +1 -0
- package/dist/client/{floatingips-Be3zLoaD.mjs → floatingips-Fa6ocNUu.mjs} +7 -7
- package/dist/client/{floatingips-Be3zLoaD.mjs.map → floatingips-Fa6ocNUu.mjs.map} +1 -1
- package/dist/client/{formatBytes-D6oa0wU9.mjs → formatBytes-tQBEnPoL.mjs} +1 -1
- package/dist/client/{formatBytes-D6oa0wU9.mjs.map → formatBytes-tQBEnPoL.mjs.map} +1 -1
- package/dist/client/{hooks-D0krAKvo.mjs → hooks-DEjb9d1F.mjs} +1 -1
- package/dist/client/{images-CCYBAphP2.mjs → images-CSFfefAu.mjs} +8 -7
- package/dist/client/images-CSFfefAu.mjs.map +1 -0
- package/dist/client/{images-BiEBENaj.mjs → images-CTLCY-yY.mjs} +2 -2
- package/dist/client/{images-BiEBENaj.mjs.map → images-CTLCY-yY.mjs.map} +1 -1
- package/dist/client/images-DM9I8G0p.mjs.map +1 -1
- package/dist/client/images-tYfyOkX8.mjs +8 -0
- package/dist/client/images-tYfyOkX8.mjs.map +1 -0
- package/dist/client/index.js +111 -109
- package/dist/client/index.js.map +1 -1
- package/dist/client/{network-nbSbl0X0.mjs → network-rYLHyf15.mjs} +1 -1
- package/dist/client/{network-nbSbl0X0.mjs.map → network-rYLHyf15.mjs.map} +1 -1
- package/dist/client/{objects-CU5ws07o.mjs → objects-BciXwZ00.mjs} +2 -2
- package/dist/client/objects-BciXwZ00.mjs.map +1 -0
- package/dist/client/{objects-GmuIOaHd.mjs → objects-Cdew99tK.mjs} +1 -1
- package/dist/client/objects-Cdew99tK.mjs.map +1 -0
- package/dist/client/objects-DaElrban.mjs +5340 -0
- package/dist/client/objects-DaElrban.mjs.map +1 -0
- package/dist/client/{overview-EhfPY8Je.mjs → overview-BMhjFMIV.mjs} +2 -2
- package/dist/client/{overview-EhfPY8Je.mjs.map → overview-BMhjFMIV.mjs.map} +1 -1
- package/dist/client/{overview-DzYBiNfD.mjs → overview-BYIRj7_X.mjs} +1 -1
- package/dist/client/{overview-DzYBiNfD.mjs.map → overview-BYIRj7_X.mjs.map} +1 -1
- package/dist/client/{overview-B3gdnWTG.mjs → overview-DRCKNBH2.mjs} +1 -1
- package/dist/client/{overview-B3gdnWTG.mjs.map → overview-DRCKNBH2.mjs.map} +1 -1
- package/dist/client/{overview-XueZI4LQ.mjs → overview-urYLOVQE.mjs} +2 -2
- package/dist/client/{overview-XueZI4LQ.mjs.map → overview-urYLOVQE.mjs.map} +1 -1
- package/dist/client/{pca-DSM71LhW.mjs → pca-COmKvp3J.mjs} +2 -2
- package/dist/client/{pca-DSM71LhW.mjs.map → pca-COmKvp3J.mjs.map} +1 -1
- package/dist/client/{pca-x9if8xU-.mjs → pca-oc7J0_Xd.mjs} +17 -17
- package/dist/client/pca-oc7J0_Xd.mjs.map +1 -0
- package/dist/client/{projects-CnmZIB2Q.mjs → projects-BUabCzvw.mjs} +16 -16
- package/dist/client/projects-BUabCzvw.mjs.map +1 -0
- package/dist/client/{projects-Bt0XptpG.mjs → projects-DI_L4oDw.mjs} +2 -2
- package/dist/client/{projects-Bt0XptpG.mjs.map → projects-DI_L4oDw.mjs.map} +1 -1
- package/dist/client/{projects-B6BPo2Ar.mjs → projects-Dl5XkXUP.mjs} +1 -1
- package/dist/client/{projects-B6BPo2Ar.mjs.map → projects-Dl5XkXUP.mjs.map} +1 -1
- package/dist/client/{projects-BilrmHLu.mjs → projects-HoQ0gE5Y.mjs} +1 -1
- package/dist/client/{projects-BilrmHLu.mjs.map → projects-HoQ0gE5Y.mjs.map} +1 -1
- package/dist/client/{securitygroups-BdzieS7Z.mjs → securitygroups-BjkmHk2J.mjs} +7 -7
- package/dist/client/{securitygroups-BdzieS7Z.mjs.map → securitygroups-BjkmHk2J.mjs.map} +1 -1
- package/dist/client/{useListWithFiltering-CqQbAjEe.mjs → useListWithFiltering-CbhHJO4V.mjs} +1 -1
- package/dist/client/{useListWithFiltering-CqQbAjEe.mjs.map → useListWithFiltering-CbhHJO4V.mjs.map} +1 -1
- package/dist/client/{useProjectId-CgOTejka.mjs → useProjectId-OQv2KBbG.mjs} +1 -1
- package/dist/client/{useProjectId-CgOTejka.mjs.map → useProjectId-OQv2KBbG.mjs.map} +1 -1
- package/dist/server/index.d.ts +7 -1
- package/dist/server/index.js +116 -55
- package/package.json +2 -3
- package/dist/client/FiltersInput-BgNaHFBt.mjs.map +0 -1
- package/dist/client/_floatingIpId-BpUfL8Im2.mjs.map +0 -1
- package/dist/client/_pcaId-CwlH1Kvl.mjs.map +0 -1
- package/dist/client/containers-BuXUVb1N.mjs.map +0 -1
- package/dist/client/containers-Ca5V1EBS.mjs.map +0 -1
- package/dist/client/containers-NW7RnHTI.mjs.map +0 -1
- package/dist/client/images-CCYBAphP2.mjs.map +0 -1
- package/dist/client/objects-CU5ws07o.mjs.map +0 -1
- package/dist/client/objects-FXN0VWLI.mjs +0 -4760
- package/dist/client/objects-FXN0VWLI.mjs.map +0 -1
- package/dist/client/objects-GmuIOaHd.mjs.map +0 -1
- package/dist/client/pca-x9if8xU-.mjs.map +0 -1
- package/dist/client/projects-CnmZIB2Q.mjs.map +0 -1
- package/permission_policies/compute.yaml +0 -975
- package/permission_policies/image.yaml +0 -71
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"securitygroups-BdzieS7Z.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","SecurityGroupTableRow","securityGroup","sg","permissions","onEdit","onDelete","onViewDetails","isReadOnly","useLingui","BooleanValue","value","span","t","handleShowDetails","data-testid","id","onClick","className","div","p","name","description","shared","project_id","stateful","e","stopPropagation","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","useLingui","navigate","projectId","selectedSecurityGroup","setSelectedSecurityGroup","editModalOpen","setEditModalOpen","deleteDialogOpen","setDeleteDialogOpen","prevIsDeletingRef","prevIsUpdatingRef","handleEdit","sg","handleDelete","handleViewDetails","to","params","securityGroupId","id","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","open","onClose","onUpdate","data","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","useState","useNavigate","Button","trpcReact","ListToolbar","buildFilterParams","useListWithFiltering","useProjectId","SecurityGroupListContainer","CreateSecurityGroupModal","SECURITY_GROUP_SHARED","TRUE","FALSE","SecurityGroups","useLingui","navigate","projectId","createModalOpen","setCreateModalOpen","deleteError","setDeleteError","createError","setCreateError","updateError","setUpdateError","searchTerm","sortSettings","filterSettings","handleSearchChange","handleSortChange","handleFilterChange","defaultSortKey","defaultSortDir","sortOptions","label","t","value","filters","displayName","filterName","values","Object","supportsMultiValue","utils","useUtils","permissions","canCreate","canUpdate","canDelete","canManageAccess","data","securityGroups","isLoading","isError","error","network","securityGroup","list","useQuery","project_id","sort_key","sortBy","sort_dir","sortDirection","enabled","createSecurityGroupMutation","create","useMutation","onSuccess","createdSecurityGroup","invalidate","to","params","securityGroupId","id","onError","message","deleteSecurityGroupMutation","deleteById","updateSecurityGroupMutation","update","handleCreateSecurityGroup","securityGroupData","mutateAsync","handleDeleteSecurityGroup","mutate","handleUpdateSecurityGroup","div","className","onSort","onFilter","onSearch","actions","onClick","variant","onCreateClick","onDeleteSecurityGroup","isDeletingSecurityGroup","isPending","onUpdateSecurityGroup","isUpdatingSecurityGroup","currentProjectId","isOpen","onClose","onCreate","useLingui","SecurityGroups","ContentHeading","RouteComponent","t","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/-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} 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 canCreate: boolean\n canUpdate: boolean\n canDelete: boolean\n canManageAccess: 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}\n\nexport function SecurityGroupTableRow({\n securityGroup: sg,\n permissions,\n onEdit,\n onDelete,\n onViewDetails,\n isReadOnly = false,\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 <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 ) => void\n isUpdatingSecurityGroup?: boolean\n updateError?: string | null\n currentProjectId?: string\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}: 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 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={5}>\n <DataGridRow>\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 />\n )\n })}\n </DataGrid>\n\n {selectedSecurityGroup && (\n <>\n <EditSecurityGroupModal\n securityGroup={selectedSecurityGroup}\n open={editModalOpen}\n onClose={closeEditModal}\n onUpdate={async (id, data) => {\n if (onUpdateSecurityGroup) {\n await onUpdateSecurityGroup(id, data)\n }\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 />\n </FormRow>\n </FormSection>\n </Form>\n )}\n </Modal>\n )\n}\n","import { useState } from \"react\"\nimport { Trans, useLingui } from \"@lingui/react/macro\"\nimport { useNavigate } from \"@tanstack/react-router\"\nimport { Button } from \"@cloudoperators/juno-ui-components\"\nimport { trpcReact } from \"@/client/trpcClient\"\nimport { ListToolbar } from \"@/client/components/ListToolbar\"\nimport { buildFilterParams } from \"@/client/utils/buildFilterParams\"\nimport { useListWithFiltering } from \"@/client/utils/useListWithFiltering\"\nimport { useProjectId } from \"@/client/hooks\"\nimport { SecurityGroupListContainer } from \"./SecurityGroupListContainer\"\nimport { CreateSecurityGroupModal } from \"./-modals/CreateSecurityGroupModal\"\nimport { CreateSecurityGroupInput, UpdateSecurityGroupInput } from \"@/server/Network/types/securityGroup\"\n\n// Security group shared filter constants\nconst SECURITY_GROUP_SHARED = {\n TRUE: \"true\",\n FALSE: \"false\",\n} as const\n\ntype SecurityGroupSortKey = \"name\" | \"project_id\"\n\nexport const SecurityGroups = () => {\n const { t } = useLingui()\n const navigate = useNavigate()\n const projectId = useProjectId()\n\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 { searchTerm, sortSettings, filterSettings, handleSearchChange, handleSortChange, handleFilterChange } =\n useListWithFiltering<SecurityGroupSortKey>({\n defaultSortKey: \"name\",\n defaultSortDir: \"asc\",\n sortOptions: [\n { label: t`Name`, value: \"name\" },\n { label: t`Project id`, value: \"project_id\" },\n ],\n filterSettings: {\n filters: [\n {\n displayName: t`Shared`,\n filterName: \"shared\",\n values: Object.values(SECURITY_GROUP_SHARED),\n supportsMultiValue: false,\n },\n ],\n },\n })\n\n const utils = trpcReact.useUtils()\n\n // TODO: replace with trpc.network.canUser when security group permissions are available\n const permissions = {\n canCreate: true,\n canUpdate: true,\n canDelete: true,\n canManageAccess: true,\n }\n\n const {\n data: securityGroups = [],\n isLoading,\n isError,\n error,\n } = trpcReact.network.securityGroup.list.useQuery(\n {\n project_id: projectId || \"\",\n sort_key: sortSettings.sortBy,\n sort_dir: sortSettings.sortDirection,\n ...buildFilterParams(filterSettings),\n ...(searchTerm ? { searchTerm } : {}),\n },\n {\n enabled: !!projectId,\n }\n )\n\n const createSecurityGroupMutation = trpcReact.network.securityGroup.create.useMutation({\n onSuccess: (createdSecurityGroup) => {\n // Invalidate and refetch the security groups list\n utils.network.securityGroup.list.invalidate()\n setCreateError(null)\n\n // Navigate to the details page of the newly created security group\n navigate({\n to: \"/projects/$projectId/network/securitygroups/$securityGroupId\",\n params: {\n projectId,\n securityGroupId: createdSecurityGroup.id,\n },\n })\n },\n onError: (error) => {\n // Backend handles error parsing, just display the message\n setCreateError(error.message || t`Failed to create security group`)\n },\n })\n\n const deleteSecurityGroupMutation = trpcReact.network.securityGroup.deleteById.useMutation({\n onSuccess: () => {\n // Invalidate and refetch the security groups list\n utils.network.securityGroup.list.invalidate()\n setDeleteError(null)\n },\n onError: (error) => {\n // Backend handles error parsing, just display the message\n setDeleteError(error.message || t`Failed to delete security group`)\n },\n })\n\n const updateSecurityGroupMutation = trpcReact.network.securityGroup.update.useMutation({\n onSuccess: () => {\n // Invalidate and refetch the security groups list\n utils.network.securityGroup.list.invalidate()\n setUpdateError(null)\n },\n onError: (error) => {\n // Backend handles error parsing, just display the message\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 return (\n <div className=\"relative\">\n <ListToolbar\n sortSettings={sortSettings}\n filterSettings={filterSettings}\n searchTerm={searchTerm}\n onSort={handleSortChange}\n onFilter={handleFilterChange}\n onSearch={handleSearchChange}\n actions={\n permissions.canCreate && (\n <Button onClick={() => setCreateModalOpen(true)} variant=\"primary\">\n <Trans>Create Security Group</Trans>\n </Button>\n )\n }\n />\n\n <SecurityGroupListContainer\n securityGroups={securityGroups}\n isLoading={isLoading}\n isError={isError}\n error={error}\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 />\n\n <CreateSecurityGroupModal\n isOpen={createModalOpen}\n onClose={() => setCreateModalOpen(false)}\n onCreate={handleCreateSecurityGroup}\n isLoading={createSecurityGroupMutation.isPending}\n error={createError}\n />\n </div>\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 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 return (\n <>\n <ContentHeading>{t`Security Groups`}</ContentHeading>\n <SecurityGroups />\n </>\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"securitygroups-BjkmHk2J.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","SecurityGroupTableRow","securityGroup","sg","permissions","onEdit","onDelete","onViewDetails","isReadOnly","useLingui","BooleanValue","value","span","t","handleShowDetails","data-testid","id","onClick","className","div","p","name","description","shared","project_id","stateful","e","stopPropagation","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","useLingui","navigate","projectId","selectedSecurityGroup","setSelectedSecurityGroup","editModalOpen","setEditModalOpen","deleteDialogOpen","setDeleteDialogOpen","prevIsDeletingRef","prevIsUpdatingRef","handleEdit","sg","handleDelete","handleViewDetails","to","params","securityGroupId","id","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","open","onClose","onUpdate","data","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","useState","useNavigate","Button","trpcReact","ListToolbar","buildFilterParams","useListWithFiltering","useProjectId","SecurityGroupListContainer","CreateSecurityGroupModal","SECURITY_GROUP_SHARED","TRUE","FALSE","SecurityGroups","useLingui","navigate","projectId","createModalOpen","setCreateModalOpen","deleteError","setDeleteError","createError","setCreateError","updateError","setUpdateError","searchTerm","sortSettings","filterSettings","handleSearchChange","handleSortChange","handleFilterChange","defaultSortKey","defaultSortDir","sortOptions","label","t","value","filters","displayName","filterName","values","Object","supportsMultiValue","utils","useUtils","permissions","canCreate","canUpdate","canDelete","canManageAccess","data","securityGroups","isLoading","isError","error","network","securityGroup","list","useQuery","project_id","sort_key","sortBy","sort_dir","sortDirection","enabled","createSecurityGroupMutation","create","useMutation","onSuccess","createdSecurityGroup","invalidate","to","params","securityGroupId","id","onError","message","deleteSecurityGroupMutation","deleteById","updateSecurityGroupMutation","update","handleCreateSecurityGroup","securityGroupData","mutateAsync","handleDeleteSecurityGroup","mutate","handleUpdateSecurityGroup","div","className","onSort","onFilter","onSearch","actions","onClick","variant","onCreateClick","onDeleteSecurityGroup","isDeletingSecurityGroup","isPending","onUpdateSecurityGroup","isUpdatingSecurityGroup","currentProjectId","isOpen","onClose","onCreate","useLingui","SecurityGroups","ContentHeading","RouteComponent","t","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/-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} 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 canCreate: boolean\n canUpdate: boolean\n canDelete: boolean\n canManageAccess: 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}\n\nexport function SecurityGroupTableRow({\n securityGroup: sg,\n permissions,\n onEdit,\n onDelete,\n onViewDetails,\n isReadOnly = false,\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 <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 ) => void\n isUpdatingSecurityGroup?: boolean\n updateError?: string | null\n currentProjectId?: string\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}: 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 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={5}>\n <DataGridRow>\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 />\n )\n })}\n </DataGrid>\n\n {selectedSecurityGroup && (\n <>\n <EditSecurityGroupModal\n securityGroup={selectedSecurityGroup}\n open={editModalOpen}\n onClose={closeEditModal}\n onUpdate={async (id, data) => {\n if (onUpdateSecurityGroup) {\n await onUpdateSecurityGroup(id, data)\n }\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 />\n </FormRow>\n </FormSection>\n </Form>\n )}\n </Modal>\n )\n}\n","import { useState } from \"react\"\nimport { Trans, useLingui } from \"@lingui/react/macro\"\nimport { useNavigate } from \"@tanstack/react-router\"\nimport { Button } from \"@cloudoperators/juno-ui-components\"\nimport { trpcReact } from \"@/client/trpcClient\"\nimport { ListToolbar } from \"@/client/components/ListToolbar\"\nimport { buildFilterParams } from \"@/client/utils/buildFilterParams\"\nimport { useListWithFiltering } from \"@/client/utils/useListWithFiltering\"\nimport { useProjectId } from \"@/client/hooks\"\nimport { SecurityGroupListContainer } from \"./SecurityGroupListContainer\"\nimport { CreateSecurityGroupModal } from \"./-modals/CreateSecurityGroupModal\"\nimport { CreateSecurityGroupInput, UpdateSecurityGroupInput } from \"@/server/Network/types/securityGroup\"\n\n// Security group shared filter constants\nconst SECURITY_GROUP_SHARED = {\n TRUE: \"true\",\n FALSE: \"false\",\n} as const\n\ntype SecurityGroupSortKey = \"name\" | \"project_id\"\n\nexport const SecurityGroups = () => {\n const { t } = useLingui()\n const navigate = useNavigate()\n const projectId = useProjectId()\n\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 { searchTerm, sortSettings, filterSettings, handleSearchChange, handleSortChange, handleFilterChange } =\n useListWithFiltering<SecurityGroupSortKey>({\n defaultSortKey: \"name\",\n defaultSortDir: \"asc\",\n sortOptions: [\n { label: t`Name`, value: \"name\" },\n { label: t`Project id`, value: \"project_id\" },\n ],\n filterSettings: {\n filters: [\n {\n displayName: t`Shared`,\n filterName: \"shared\",\n values: Object.values(SECURITY_GROUP_SHARED),\n supportsMultiValue: false,\n },\n ],\n },\n })\n\n const utils = trpcReact.useUtils()\n\n // TODO: replace with trpc.network.canUser when security group permissions are available\n const permissions = {\n canCreate: true,\n canUpdate: true,\n canDelete: true,\n canManageAccess: true,\n }\n\n const {\n data: securityGroups = [],\n isLoading,\n isError,\n error,\n } = trpcReact.network.securityGroup.list.useQuery(\n {\n project_id: projectId || \"\",\n sort_key: sortSettings.sortBy,\n sort_dir: sortSettings.sortDirection,\n ...buildFilterParams(filterSettings),\n ...(searchTerm ? { searchTerm } : {}),\n },\n {\n enabled: !!projectId,\n }\n )\n\n const createSecurityGroupMutation = trpcReact.network.securityGroup.create.useMutation({\n onSuccess: (createdSecurityGroup) => {\n // Invalidate and refetch the security groups list\n utils.network.securityGroup.list.invalidate()\n setCreateError(null)\n\n // Navigate to the details page of the newly created security group\n navigate({\n to: \"/projects/$projectId/network/securitygroups/$securityGroupId\",\n params: {\n projectId,\n securityGroupId: createdSecurityGroup.id,\n },\n })\n },\n onError: (error) => {\n // Backend handles error parsing, just display the message\n setCreateError(error.message || t`Failed to create security group`)\n },\n })\n\n const deleteSecurityGroupMutation = trpcReact.network.securityGroup.deleteById.useMutation({\n onSuccess: () => {\n // Invalidate and refetch the security groups list\n utils.network.securityGroup.list.invalidate()\n setDeleteError(null)\n },\n onError: (error) => {\n // Backend handles error parsing, just display the message\n setDeleteError(error.message || t`Failed to delete security group`)\n },\n })\n\n const updateSecurityGroupMutation = trpcReact.network.securityGroup.update.useMutation({\n onSuccess: () => {\n // Invalidate and refetch the security groups list\n utils.network.securityGroup.list.invalidate()\n setUpdateError(null)\n },\n onError: (error) => {\n // Backend handles error parsing, just display the message\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 return (\n <div className=\"relative\">\n <ListToolbar\n sortSettings={sortSettings}\n filterSettings={filterSettings}\n searchTerm={searchTerm}\n onSort={handleSortChange}\n onFilter={handleFilterChange}\n onSearch={handleSearchChange}\n actions={\n permissions.canCreate && (\n <Button onClick={() => setCreateModalOpen(true)} variant=\"primary\">\n <Trans>Create Security Group</Trans>\n </Button>\n )\n }\n />\n\n <SecurityGroupListContainer\n securityGroups={securityGroups}\n isLoading={isLoading}\n isError={isError}\n error={error}\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 />\n\n <CreateSecurityGroupModal\n isOpen={createModalOpen}\n onClose={() => setCreateModalOpen(false)}\n onCreate={handleCreateSecurityGroup}\n isLoading={createSecurityGroupMutation.isPending}\n error={createError}\n />\n </div>\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 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 return (\n <>\n <ContentHeading>{t`Security Groups`}</ContentHeading>\n <SecurityGroups />\n </>\n )\n}\n"],"mappings":"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"}
|
package/dist/client/{useListWithFiltering-CqQbAjEe.mjs.map → useListWithFiltering-CbhHJO4V.mjs.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useListWithFiltering-
|
|
1
|
+
{"version":3,"file":"useListWithFiltering-CbhHJO4V.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,GAAA,EACvCS,KAAsBC,MAAAA;EAC1B,IAAMC,IAAc,OAAOD,KAAS,WAAWA,IAAO;AACtDX,UAAsBS,EAAcG,EAAAA,CAAAA;IAIhC,CAACC,GAAcC,KAAmBb,EAA4B;EAClEc,SAASV;EACTW,QAAQb;EACRc,eAAeb;EACjB,CAAA,EACMc,KAAoBC,MAAAA;AAMxBL,IALoC;GAClCC,SAASI,EAAgBJ,WAAWF,EAAaE;GACjDC,QAAQ,EAAiBA,QAAQK,UAAAA,IAAoBlB;GACrDc,eAAe,EAAiBA,iBAAmCb;GACrE,CACgBgB;IAIZ,CAACd,GAAgBgB,KAAqBrB,EAAyBM,EAAAA;AAKrE,QAAO;EACLC;EACAK;EACAP;EACAI;EACAQ;EACAK,qBAV0BC,MAAAA;AAC1BxB,WAAsBsB,EAAkBE,EAAAA,CAAAA;;EAU1C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useProjectId-
|
|
1
|
+
{"version":3,"file":"useProjectId-OQv2KBbG.mjs","names":["useParams","useProjectId","projectId","strict","Error"],"sources":["../../src/client/hooks/useProjectId.ts"],"sourcesContent":["import { useParams } from \"@tanstack/react-router\"\n\n/**\n * Extract projectId from the current URL.\n *\n * This hook works with both old and new route structures:\n * - Old: /accounts/:accountId/projects/:projectId/...\n * - New: /projects/:projectId/...\n *\n * @throws {Error} If used outside of a project route context\n * @returns {string} The current project ID from URL params\n *\n * @example\n * ```tsx\n * function SecurityGroupsList() {\n * const projectId = useProjectId()\n *\n * const { data } = trpc.network.securityGroup.list.useQuery({\n * project_id: projectId\n * })\n * }\n * ```\n */\nexport function useProjectId(): string {\n // Use strict: false to work with any route that has projectId param\n // Works with both:\n // - /accounts/:accountId/projects/:projectId/... (old)\n // - /projects/:projectId/... (new)\n const { projectId } = useParams({\n strict: false,\n })\n\n // Runtime validation - provides clear error message if projectId is not in URL\n if (!projectId) {\n throw new Error(\n \"useProjectId() must be used within a project-scoped route. \" +\n \"This is likely a routing configuration error. \" +\n \"Expected route pattern with :projectId parameter\"\n )\n }\n\n return projectId\n}\n"],"mappings":";;AAuBA,SAAgBC,IAAAA;CAKd,IAAM,EAAEC,iBAAcF,EAAU,EAC9BG,QAAQ,IACV,CAAA;AAGA,KAAI,CAACD,EACH,OAAUE,MACR,4JAEE;AAIN,QAAOF"}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -28,8 +28,14 @@ interface AuroraServerConfig {
|
|
|
28
28
|
* Never set this in production.
|
|
29
29
|
*/
|
|
30
30
|
insecureCookies?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Absolute path to a directory containing consumer-supplied OpenStack policy
|
|
33
|
+
* files (e.g. compute.yaml, image.yaml). Files found here take precedence
|
|
34
|
+
* over the legacy in-tree permission_custom_policies/ directory.
|
|
35
|
+
*/
|
|
36
|
+
policyDir: string;
|
|
31
37
|
}
|
|
32
38
|
|
|
33
|
-
declare function createServer(config
|
|
39
|
+
declare function createServer(config: AuroraServerConfig): Promise<FastifyInstance>;
|
|
34
40
|
|
|
35
41
|
export { type AuroraServerConfig, createServer };
|
package/dist/server/index.js
CHANGED
|
@@ -30787,17 +30787,25 @@ var import_server8 = require("@trpc/server");
|
|
|
30787
30787
|
var import_path = __toESM(require("path"));
|
|
30788
30788
|
var import_fs = __toESM(require("fs"));
|
|
30789
30789
|
var import_policy_engine = __toESM(require_esm());
|
|
30790
|
-
var loadPolicyEngine = /* @__PURE__ */ __name((fileName) => {
|
|
30791
|
-
|
|
30792
|
-
|
|
30793
|
-
|
|
30790
|
+
var loadPolicyEngine = /* @__PURE__ */ __name((fileName, consumerDir) => {
|
|
30791
|
+
if (!import_path.default.isAbsolute(consumerDir)) {
|
|
30792
|
+
throw new Error(`policyDir must be an absolute path, got: "${consumerDir}"`);
|
|
30793
|
+
}
|
|
30794
|
+
const candidates = [
|
|
30795
|
+
import_path.default.join(consumerDir, fileName),
|
|
30796
|
+
import_path.default.join(__dirname, `../../permission_custom_policies/${fileName}`)
|
|
30797
|
+
];
|
|
30798
|
+
const file = candidates.find((p) => import_fs.default.existsSync(p));
|
|
30799
|
+
if (!file) {
|
|
30800
|
+
throw new Error(`Policy file "${fileName}" not found. Searched:
|
|
30801
|
+
${candidates.map((p) => ` ${p}`).join("\n")}
|
|
30802
|
+
Pass an absolute path via the policyDir option in createServer.`);
|
|
30803
|
+
}
|
|
30794
30804
|
return (0, import_policy_engine.createPolicyEngineFromFile)(file);
|
|
30795
30805
|
}, "loadPolicyEngine");
|
|
30796
30806
|
|
|
30797
30807
|
// src/server/Compute/routers/permissionRouter.ts
|
|
30798
|
-
var
|
|
30799
|
-
var imagePolicyEngine = loadPolicyEngine("image.yaml");
|
|
30800
|
-
var getPolicy = /* @__PURE__ */ __name((ctx, policyEngineName) => {
|
|
30808
|
+
var getPolicy = /* @__PURE__ */ __name((ctx, policyEngineName, engines) => {
|
|
30801
30809
|
const openstackSession = ctx.openstack;
|
|
30802
30810
|
const token = openstackSession?.getToken();
|
|
30803
30811
|
if (!token) {
|
|
@@ -30806,7 +30814,7 @@ var getPolicy = /* @__PURE__ */ __name((ctx, policyEngineName) => {
|
|
|
30806
30814
|
message: "No valid OpenStack token found"
|
|
30807
30815
|
});
|
|
30808
30816
|
}
|
|
30809
|
-
const policyEngine = policyEngineName === "compute" ?
|
|
30817
|
+
const policyEngine = policyEngineName === "compute" ? engines.compute : engines.image;
|
|
30810
30818
|
return policyEngine.policy(token.tokenData, {
|
|
30811
30819
|
debug: true,
|
|
30812
30820
|
defaultParams: {
|
|
@@ -30904,9 +30912,9 @@ var POLICY_MAPPINGS = {
|
|
|
30904
30912
|
rule: "modify_member"
|
|
30905
30913
|
}
|
|
30906
30914
|
};
|
|
30907
|
-
var checkSinglePermission = /* @__PURE__ */ __name((ctx, permission) => {
|
|
30915
|
+
var checkSinglePermission = /* @__PURE__ */ __name((ctx, permission, engines) => {
|
|
30908
30916
|
const policyMapping = POLICY_MAPPINGS[permission];
|
|
30909
|
-
const policy = getPolicy(ctx, policyMapping.engine);
|
|
30917
|
+
const policy = getPolicy(ctx, policyMapping.engine, engines);
|
|
30910
30918
|
return policy.check(policyMapping.rule);
|
|
30911
30919
|
}, "checkSinglePermission");
|
|
30912
30920
|
var PERMISSION_KEY = import_zod10.z.string().superRefine((value, ctx) => {
|
|
@@ -30917,34 +30925,40 @@ var PERMISSION_KEY = import_zod10.z.string().superRefine((value, ctx) => {
|
|
|
30917
30925
|
});
|
|
30918
30926
|
}
|
|
30919
30927
|
}).transform((value) => value);
|
|
30920
|
-
var
|
|
30921
|
-
|
|
30922
|
-
|
|
30923
|
-
|
|
30924
|
-
|
|
30925
|
-
|
|
30926
|
-
|
|
30927
|
-
|
|
30928
|
-
|
|
30929
|
-
|
|
30930
|
-
|
|
30931
|
-
|
|
30932
|
-
|
|
30933
|
-
|
|
30934
|
-
|
|
30935
|
-
|
|
30928
|
+
var buildPermissionRouter = /* @__PURE__ */ __name((policyDir) => {
|
|
30929
|
+
const engines = {
|
|
30930
|
+
compute: loadPolicyEngine("compute.yaml", policyDir),
|
|
30931
|
+
image: loadPolicyEngine("image.yaml", policyDir)
|
|
30932
|
+
};
|
|
30933
|
+
return {
|
|
30934
|
+
canUser: projectScopedProcedure.input(projectScopedInputSchema.extend({
|
|
30935
|
+
permission: import_zod10.z.union([
|
|
30936
|
+
PERMISSION_KEY,
|
|
30937
|
+
import_zod10.z.array(PERMISSION_KEY)
|
|
30938
|
+
])
|
|
30939
|
+
})).query(async ({ ctx, input }) => {
|
|
30940
|
+
const permissions = typeof input.permission === "string" ? [
|
|
30941
|
+
input.permission
|
|
30942
|
+
] : input.permission;
|
|
30943
|
+
if (permissions.length === 0) {
|
|
30944
|
+
return [];
|
|
30945
|
+
}
|
|
30946
|
+
return permissions.map((permission) => checkSinglePermission(ctx, permission, engines));
|
|
30947
|
+
})
|
|
30948
|
+
};
|
|
30949
|
+
}, "buildPermissionRouter");
|
|
30936
30950
|
|
|
30937
30951
|
// src/server/Compute/routers/index.ts
|
|
30938
|
-
var
|
|
30952
|
+
var buildComputeRouters = /* @__PURE__ */ __name((policyDir) => ({
|
|
30939
30953
|
compute: auroraRouter({
|
|
30940
30954
|
...serverRouter,
|
|
30941
30955
|
...flavorRouter,
|
|
30942
30956
|
...imageRouter,
|
|
30943
30957
|
...keypairRouter,
|
|
30944
30958
|
...serverGroupRouter,
|
|
30945
|
-
...
|
|
30959
|
+
...buildPermissionRouter(policyDir)
|
|
30946
30960
|
})
|
|
30947
|
-
};
|
|
30961
|
+
}), "buildComputeRouters");
|
|
30948
30962
|
|
|
30949
30963
|
// src/server/Storage/routers/swift/swiftRouter.ts
|
|
30950
30964
|
var import_server10 = require("@trpc/server");
|
|
@@ -33283,7 +33297,11 @@ function mapS3ErrorToTRPCError(error, context) {
|
|
|
33283
33297
|
];
|
|
33284
33298
|
if (context.bucket) parts.push(`bucket: ${context.bucket}`);
|
|
33285
33299
|
if (context.key) parts.push(`key: ${context.key}`);
|
|
33286
|
-
if (
|
|
33300
|
+
if (errorCode === "AccessDenied") {
|
|
33301
|
+
parts.push("Access denied \u2014 your credentials are valid but lack permissions for this operation");
|
|
33302
|
+
} else if (errorCode === "InvalidAccessKeyId" || errorCode === "SignatureDoesNotMatch" || errorCode === "TokenRefreshRequired" || errorCode === "RequestTimeTooSkewed") {
|
|
33303
|
+
parts.push("Invalid access key \u2014 your credentials may be expired or incorrect");
|
|
33304
|
+
} else if (s3Error.message) {
|
|
33287
33305
|
parts.push(s3Error.message);
|
|
33288
33306
|
}
|
|
33289
33307
|
throw new import_server12.TRPCError({
|
|
@@ -33319,7 +33337,12 @@ var listContainersInputSchema2 = projectScopedInputSchema.extend({
|
|
|
33319
33337
|
includeMetadata: import_zod13.z.boolean().optional().default(false)
|
|
33320
33338
|
});
|
|
33321
33339
|
var createBucketInputSchema = projectScopedInputSchema.extend({
|
|
33322
|
-
bucketName: import_zod13.z.string().min(3).max(63)
|
|
33340
|
+
bucketName: import_zod13.z.string().min(3).max(63),
|
|
33341
|
+
enableVersioning: import_zod13.z.boolean().optional().default(false)
|
|
33342
|
+
});
|
|
33343
|
+
var createBucketOutputSchema = import_zod13.z.object({
|
|
33344
|
+
success: import_zod13.z.boolean(),
|
|
33345
|
+
versioningError: import_zod13.z.string().optional()
|
|
33323
33346
|
});
|
|
33324
33347
|
var deleteBucketInputSchema = projectScopedInputSchema.extend({
|
|
33325
33348
|
bucketName: import_zod13.z.string().min(1)
|
|
@@ -33523,6 +33546,10 @@ var containerRouter = {
|
|
|
33523
33546
|
* Uses AWS SDK CreateBucketCommand. The AWS SDK automatically adds LocationConstraint
|
|
33524
33547
|
* based on the region configured in the S3 client (resolved from OpenStack service catalog).
|
|
33525
33548
|
*
|
|
33549
|
+
* If enableVersioning is true, enables versioning immediately after bucket creation using
|
|
33550
|
+
* PutBucketVersioningCommand. If versioning fails, the bucket is still created successfully
|
|
33551
|
+
* but the response includes a versioningError field with the error message.
|
|
33552
|
+
*
|
|
33526
33553
|
* Bucket naming rules (validated client-side and by S3 API):
|
|
33527
33554
|
* - 3-63 characters
|
|
33528
33555
|
* - Lowercase letters, numbers, hyphens, periods only
|
|
@@ -33536,18 +33563,37 @@ var containerRouter = {
|
|
|
33536
33563
|
*/
|
|
33537
33564
|
create: cephProtectedProcedure.input(createBucketInputSchema).mutation(async ({ ctx, input }) => {
|
|
33538
33565
|
const s3 = ctx.getCephClient();
|
|
33539
|
-
const { bucketName } = input;
|
|
33566
|
+
const { bucketName, enableVersioning } = input;
|
|
33540
33567
|
try {
|
|
33541
33568
|
await s3.send(new import_client_s32.CreateBucketCommand({
|
|
33542
33569
|
Bucket: bucketName
|
|
33543
33570
|
}));
|
|
33544
|
-
return true;
|
|
33545
33571
|
} catch (error) {
|
|
33546
33572
|
throw mapS3ErrorToTRPCError(error, {
|
|
33547
33573
|
operation: "create bucket",
|
|
33548
33574
|
bucket: bucketName
|
|
33549
33575
|
});
|
|
33550
33576
|
}
|
|
33577
|
+
if (enableVersioning) {
|
|
33578
|
+
try {
|
|
33579
|
+
await s3.send(new import_client_s32.PutBucketVersioningCommand({
|
|
33580
|
+
Bucket: bucketName,
|
|
33581
|
+
VersioningConfiguration: {
|
|
33582
|
+
Status: "Enabled"
|
|
33583
|
+
}
|
|
33584
|
+
}));
|
|
33585
|
+
} catch (error) {
|
|
33586
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
33587
|
+
console.warn(`[s3] Bucket '${bucketName}' created successfully, but failed to enable versioning:`, errorMessage);
|
|
33588
|
+
return {
|
|
33589
|
+
success: true,
|
|
33590
|
+
versioningError: `Failed to enable versioning: ${errorMessage}`
|
|
33591
|
+
};
|
|
33592
|
+
}
|
|
33593
|
+
}
|
|
33594
|
+
return {
|
|
33595
|
+
success: true
|
|
33596
|
+
};
|
|
33551
33597
|
}),
|
|
33552
33598
|
/**
|
|
33553
33599
|
* Delete an empty S3 bucket.
|
|
@@ -34411,6 +34457,7 @@ var baseProjectSchema = import_zod17.z.object({
|
|
|
34411
34457
|
}).optional()
|
|
34412
34458
|
});
|
|
34413
34459
|
var projectSchema = baseProjectSchema.extend({
|
|
34460
|
+
domain_name: import_zod17.z.string().optional(),
|
|
34414
34461
|
parents: import_zod17.z.array(import_zod17.z.object({
|
|
34415
34462
|
project: baseProjectSchema
|
|
34416
34463
|
})).optional()
|
|
@@ -34498,14 +34545,32 @@ var projectRouter = {
|
|
|
34498
34545
|
message: "No auth token available"
|
|
34499
34546
|
});
|
|
34500
34547
|
}
|
|
34501
|
-
const response = await
|
|
34548
|
+
const [response, domainsResponse] = await Promise.all([
|
|
34549
|
+
callIdentityAPI(ctx.identityEndpoint, token.authToken, "auth/projects"),
|
|
34550
|
+
callIdentityAPI(ctx.identityEndpoint, token.authToken, "auth/domains").catch(() => null)
|
|
34551
|
+
]);
|
|
34502
34552
|
const data = await response.json();
|
|
34503
34553
|
const parsedData = projectsResponseSchema.safeParse(data);
|
|
34504
34554
|
if (!parsedData.success) {
|
|
34505
34555
|
console.error("Zod Parsing Error:", parsedData.error.message);
|
|
34506
34556
|
return void 0;
|
|
34507
34557
|
}
|
|
34508
|
-
|
|
34558
|
+
const domainsData = domainsResponse ? await domainsResponse.json().catch(() => null) : null;
|
|
34559
|
+
const domainsResponseSchema = import_zod18.z.object({
|
|
34560
|
+
domains: import_zod18.z.array(import_zod18.z.object({
|
|
34561
|
+
id: import_zod18.z.string(),
|
|
34562
|
+
name: import_zod18.z.string()
|
|
34563
|
+
}))
|
|
34564
|
+
});
|
|
34565
|
+
const parsedDomains = domainsResponseSchema.safeParse(domainsData);
|
|
34566
|
+
const domainMap = new Map((parsedDomains.success ? parsedDomains.data.domains : []).map((d) => [
|
|
34567
|
+
d.id,
|
|
34568
|
+
d.name
|
|
34569
|
+
]));
|
|
34570
|
+
return parsedData.data.projects.map((project) => ({
|
|
34571
|
+
...project,
|
|
34572
|
+
domain_name: project.domain_id ? domainMap.get(project.domain_id) ?? void 0 : void 0
|
|
34573
|
+
}));
|
|
34509
34574
|
}),
|
|
34510
34575
|
/**
|
|
34511
34576
|
* Search projects with optional text filtering
|
|
@@ -36105,9 +36170,6 @@ var CertificateAuthoritySchema = import_zod23.z.object({
|
|
|
36105
36170
|
/** Current operational state of Certificate Authority. */
|
|
36106
36171
|
state: CertificateAuthorityStateSchema
|
|
36107
36172
|
});
|
|
36108
|
-
var CertificateAuthorityResponseSchema = import_zod23.z.object({
|
|
36109
|
-
certificate_authority: CertificateAuthoritySchema
|
|
36110
|
-
});
|
|
36111
36173
|
var CertificateAuthoritiesListSchema = import_zod23.z.object({
|
|
36112
36174
|
certificate_authorities: import_zod23.z.array(CertificateAuthoritySchema)
|
|
36113
36175
|
});
|
|
@@ -36200,16 +36262,14 @@ var pcaRouter = {
|
|
|
36200
36262
|
*/
|
|
36201
36263
|
import: projectScopedProcedure.input(CertificateAuthorityImportInputSchema).mutation(async ({ input, ctx }) => {
|
|
36202
36264
|
return withErrorHandling3(async () => {
|
|
36203
|
-
const pca = ctx.openstack?.service("
|
|
36204
|
-
validateOpenstackService(pca, "
|
|
36265
|
+
const pca = ctx.openstack?.service("pca");
|
|
36266
|
+
validateOpenstackService(pca, "pca");
|
|
36205
36267
|
const url = `${PCA_BASE_URL}/${input.certificate_authority_id}:importCertificate`;
|
|
36206
36268
|
const response = await pca.post(url, {
|
|
36207
|
-
|
|
36208
|
-
imported_certificate_chain: input.imported_certificate_chain
|
|
36209
|
-
})
|
|
36269
|
+
imported_certificate_chain: input.imported_certificate_chain
|
|
36210
36270
|
});
|
|
36211
36271
|
const data = await response.json();
|
|
36212
|
-
return parseOrThrow(
|
|
36272
|
+
return parseOrThrow(CertificateAuthoritySchema, data, "pcaRouter.import");
|
|
36213
36273
|
}, "import certificate of certificate authority");
|
|
36214
36274
|
}),
|
|
36215
36275
|
listCertificates: projectScopedProcedure.input(CertificateAuthorityIdInputSchema).query(async ({ input, ctx }) => {
|
|
@@ -36252,7 +36312,7 @@ var serviceRouters = {
|
|
|
36252
36312
|
};
|
|
36253
36313
|
|
|
36254
36314
|
// src/server/routers.ts
|
|
36255
|
-
var
|
|
36315
|
+
var buildAppRouter = /* @__PURE__ */ __name((policyDir) => mergeRouters(auroraRouter(authRouters), auroraRouter(buildComputeRouters(policyDir)), auroraRouter(objectStorageRouters), auroraRouter(projectRouters), auroraRouter(networkRouters), auroraRouter(serviceRouters)), "buildAppRouter");
|
|
36256
36316
|
|
|
36257
36317
|
// src/server/context.ts
|
|
36258
36318
|
var import_signal_openstack = __toESM(require_esm2());
|
|
@@ -36636,19 +36696,20 @@ var csrfProtection_default = (0, import_fastify_plugin.default)(csrfPlugin, {
|
|
|
36636
36696
|
// src/server/server.ts
|
|
36637
36697
|
async function createServer(config) {
|
|
36638
36698
|
const isProduction = process.env.NODE_ENV === "production";
|
|
36639
|
-
const rawBffEndpoint = config
|
|
36699
|
+
const rawBffEndpoint = config.bffEndpoint ?? "/polaris-bff";
|
|
36640
36700
|
const bffEndpoint = "/" + rawBffEndpoint.split("/").filter(Boolean).join("/");
|
|
36641
|
-
const viteRoot = config
|
|
36701
|
+
const viteRoot = config.viteRoot ?? import_path2.default.resolve(__dirname, "../../");
|
|
36642
36702
|
const contextConfig = {
|
|
36643
|
-
identityEndpoint: config
|
|
36644
|
-
defaultEndpointInterface: config
|
|
36645
|
-
proxyUrl: config
|
|
36646
|
-
cephRegion: config
|
|
36647
|
-
imageMetadataExcludedProperties: config
|
|
36648
|
-
cookieName: config
|
|
36649
|
-
crossDomainCookie: config
|
|
36650
|
-
insecureCookies: config
|
|
36703
|
+
identityEndpoint: config.identityEndpoint ?? "",
|
|
36704
|
+
defaultEndpointInterface: config.defaultEndpointInterface,
|
|
36705
|
+
proxyUrl: config.proxyUrl,
|
|
36706
|
+
cephRegion: config.cephRegion,
|
|
36707
|
+
imageMetadataExcludedProperties: config.imageMetadataExcludedProperties,
|
|
36708
|
+
cookieName: config.cookieName,
|
|
36709
|
+
crossDomainCookie: config.crossDomainCookie,
|
|
36710
|
+
insecureCookies: config.insecureCookies
|
|
36651
36711
|
};
|
|
36712
|
+
const appRouter = buildAppRouter(config.policyDir);
|
|
36652
36713
|
const server = (0, import_fastify.default)({
|
|
36653
36714
|
logger: true,
|
|
36654
36715
|
bodyLimit: 1 * 1024 * 1024,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cobaltcore-dev/aurora",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Aurora OpenStack dashboard — server and client library",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -13,8 +13,7 @@
|
|
|
13
13
|
"node": ">=18.0.0"
|
|
14
14
|
},
|
|
15
15
|
"files": [
|
|
16
|
-
"dist"
|
|
17
|
-
"permission_policies"
|
|
16
|
+
"dist"
|
|
18
17
|
],
|
|
19
18
|
"publishConfig": {
|
|
20
19
|
"access": "public"
|