From 3c1b2e4fa9adeb689169deb81d0723633fa98b94 Mon Sep 17 00:00:00 2001 From: SquirrelDevelopper Date: Tue, 18 Feb 2025 14:08:37 +0100 Subject: [PATCH 1/4] Add custom vaults management for playbooks repositories Introduced APIs and UI components to manage custom vaults tied to playbook repositories. Updated the database schema, routes, and types to support vaults creation, fetching, and storage. --- .../subcomponents/CustomVaultsModal.tsx | 95 +++++++++++++++++++ .../PlaybooksGitRepositoryModal.tsx | 2 + .../services/rest/playbooks-repositories.ts | 23 +++++ .../platbooks-repository.validator.ts | 5 + .../playbooks-repository.ts | 33 +++++++ .../database/model/PlaybooksRepository.ts | 25 +++++ server/src/routes/playbooks-repository.ts | 7 ++ shared-lib/src/types/api.ts | 6 ++ 8 files changed, 196 insertions(+) create mode 100644 client/src/pages/Admin/Settings/components/subcomponents/CustomVaultsModal.tsx diff --git a/client/src/pages/Admin/Settings/components/subcomponents/CustomVaultsModal.tsx b/client/src/pages/Admin/Settings/components/subcomponents/CustomVaultsModal.tsx new file mode 100644 index 00000000..bd3b581f --- /dev/null +++ b/client/src/pages/Admin/Settings/components/subcomponents/CustomVaultsModal.tsx @@ -0,0 +1,95 @@ +import { UserSecret } from '@/components/Icons/CustomIcons'; +import { + EditableProTable, + ModalForm, + ProColumns, +} from '@ant-design/pro-components'; +import { Button } from 'antd'; +import React, { useState } from 'react'; +import { API } from 'ssm-shared-lib'; + +const CustomVaultsModal = () => { + const [dataSource, setDataSource] = useState([]); + const [editableKeys, setEditableRowKeys] = useState([]); + const columns: ProColumns[] = [ + { + title: 'Vault ID', + tooltip: 'The vault id used in your playbooks', + key: 'vaultId', + valueType: 'text', + initialValue: 'new', + }, + { + title: 'Password', + key: 'password', + dataIndex: 'password', + valueType: 'password', + }, + { + title: 'Options', + valueType: 'option', + render: (text, record, _, action) => [ + { + action?.startEditable?.(record.vaultId); + }} + > + Save + , + { + setDataSource( + dataSource.filter( + (item: API.CustomVault) => item.vaultId !== record.vaultId, + ), + ); + }} + > + Delete + , + ], + }, + ]; + return ( + }> + Manage Vaults + + } + > + + rowKey="vaultId" + recordCreatorProps={{ + position: 'bottom', + record: () => ({ + vaultId: 'new', + password: '', + }), + }} + loading={false} + columns={columns} + request={async () => ({ + data: [], + total: 3, + success: true, + })} + value={dataSource} + onChange={setDataSource} + editable={{ + type: 'single', + editableKeys, + onSave: async (rowKey, data, row) => { + console.log(rowKey, data, row); + }, + onChange: setEditableRowKeys, + }} + /> + + ); +}; + +export default CustomVaultsModal; diff --git a/client/src/pages/Admin/Settings/components/subcomponents/PlaybooksGitRepositoryModal.tsx b/client/src/pages/Admin/Settings/components/subcomponents/PlaybooksGitRepositoryModal.tsx index 7d92e22b..fd82d040 100644 --- a/client/src/pages/Admin/Settings/components/subcomponents/PlaybooksGitRepositoryModal.tsx +++ b/client/src/pages/Admin/Settings/components/subcomponents/PlaybooksGitRepositoryModal.tsx @@ -1,4 +1,5 @@ import { SimpleIconsGit } from '@/components/Icons/CustomIcons'; +import CustomVaultsModal from '@/pages/Admin/Settings/components/subcomponents/CustomVaultsModal'; import DirectoryExclusionForm from '@/pages/Admin/Settings/components/subcomponents/forms/DirectoryExclusionForm'; import GitForm from '@/pages/Admin/Settings/components/subcomponents/forms/GitForm'; import { @@ -116,6 +117,7 @@ const PlaybooksGitRepositoryModal: React.FC< const editionMode = props.selectedRecord ? [ + , + + + + + } + > + + ghost={true} + itemCardProps={{ + ghost: true, + }} + pagination={ + customVaults?.length > 8 + ? { + defaultPageSize: 8, + showSizeChanger: false, + showQuickJumper: false, + } + : false + } + rowSelection={false} + grid={{ gutter: 0, xs: 1, sm: 2, md: 2, lg: 2, xl: 4, xxl: 4 }} + onItem={(record: API.AnsibleVault) => { + return { + onMouseEnter: () => { + console.log(record); + }, + onClick: () => { + setSelectedVaultRecord(record); + setVaultModalOpened(true); + }, + }; + }} + metas={{ + title: { + dataIndex: 'vaultId', + }, + avatar: { + render: () => } />, + }, + }} + dataSource={customVaults} + /> + ); }; diff --git a/client/src/pages/Admin/Settings/components/subcomponents/CustomVaultModal.tsx b/client/src/pages/Admin/Settings/components/subcomponents/CustomVaultModal.tsx new file mode 100644 index 00000000..90c2ca52 --- /dev/null +++ b/client/src/pages/Admin/Settings/components/subcomponents/CustomVaultModal.tsx @@ -0,0 +1,136 @@ +import { UserSecret } from '@/components/Icons/CustomIcons'; +import { + deleteAnsibleVault, + postAnsibleVault, + updateAnsibleVault, +} from '@/services/rest/ansible'; +import { DeleteOutlined } from '@ant-design/icons'; +import { ModalForm, ProForm, ProFormText } from '@ant-design/pro-components'; +import { Avatar, Button, message, Popconfirm } from 'antd'; +import React, { FC, useState } from 'react'; +import { API } from 'ssm-shared-lib'; + +type CustomVaultModalProps = { + selectedRecord?: Partial; + modalOpened: boolean; + setModalOpened: any; + asyncFetch: () => Promise; + vaults: API.CustomVault[]; +}; + +const PlaybooksLocalRepositoryModal: FC = ({ + selectedRecord, + modalOpened, + setModalOpened, + asyncFetch, + vaults, +}) => { + const [loading, setLoading] = useState(false); + + const editionMode = selectedRecord + ? [ + { + setLoading(true); + if (selectedRecord && selectedRecord.vaultId) { + await deleteAnsibleVault(selectedRecord.vaultId) + .then(() => + message.warning({ + content: 'Vault deleted', + duration: 5, + }), + ) + .finally(() => { + setModalOpened(false); + }); + await asyncFetch(); + } + setLoading(false); + }} + > + + , + ] + : []; + return ( + + title={ + <> + } + /> + {(selectedRecord && <>Edit vault {selectedRecord?.vaultId}) || ( + <>Add a new vault + )} + + } + open={modalOpened} + autoFocusFirstInput + modalProps={{ + destroyOnClose: true, + onCancel: () => setModalOpened(false), + }} + onFinish={async (values) => { + if (selectedRecord) { + await updateAnsibleVault(values); + setModalOpened(false); + await asyncFetch(); + } else { + await postAnsibleVault(values); + setModalOpened(false); + await asyncFetch(); + } + }} + submitter={{ + searchConfig: { + submitText: 'Save', + }, + render: (_, defaultDoms) => { + return [...editionMode, ...defaultDoms]; + }, + }} + > + + e.vaultId === value) === undefined || + selectedRecord?.vaultId === value + ) { + return Promise.resolve(); + } + return Promise.reject('Vault ID already exists'); + }, + }, + ]} + /> + + + + ); +}; + +export default PlaybooksLocalRepositoryModal; diff --git a/client/src/pages/Admin/Settings/components/subcomponents/CustomVaultsModal.tsx b/client/src/pages/Admin/Settings/components/subcomponents/CustomVaultsModal.tsx deleted file mode 100644 index bd3b581f..00000000 --- a/client/src/pages/Admin/Settings/components/subcomponents/CustomVaultsModal.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { UserSecret } from '@/components/Icons/CustomIcons'; -import { - EditableProTable, - ModalForm, - ProColumns, -} from '@ant-design/pro-components'; -import { Button } from 'antd'; -import React, { useState } from 'react'; -import { API } from 'ssm-shared-lib'; - -const CustomVaultsModal = () => { - const [dataSource, setDataSource] = useState([]); - const [editableKeys, setEditableRowKeys] = useState([]); - const columns: ProColumns[] = [ - { - title: 'Vault ID', - tooltip: 'The vault id used in your playbooks', - key: 'vaultId', - valueType: 'text', - initialValue: 'new', - }, - { - title: 'Password', - key: 'password', - dataIndex: 'password', - valueType: 'password', - }, - { - title: 'Options', - valueType: 'option', - render: (text, record, _, action) => [ - { - action?.startEditable?.(record.vaultId); - }} - > - Save - , - { - setDataSource( - dataSource.filter( - (item: API.CustomVault) => item.vaultId !== record.vaultId, - ), - ); - }} - > - Delete - , - ], - }, - ]; - return ( - }> - Manage Vaults - - } - > - - rowKey="vaultId" - recordCreatorProps={{ - position: 'bottom', - record: () => ({ - vaultId: 'new', - password: '', - }), - }} - loading={false} - columns={columns} - request={async () => ({ - data: [], - total: 3, - success: true, - })} - value={dataSource} - onChange={setDataSource} - editable={{ - type: 'single', - editableKeys, - onSave: async (rowKey, data, row) => { - console.log(rowKey, data, row); - }, - onChange: setEditableRowKeys, - }} - /> - - ); -}; - -export default CustomVaultsModal; diff --git a/client/src/pages/Admin/Settings/components/subcomponents/PlaybooksGitRepositoryModal.tsx b/client/src/pages/Admin/Settings/components/subcomponents/PlaybooksGitRepositoryModal.tsx index fd82d040..1b30396b 100644 --- a/client/src/pages/Admin/Settings/components/subcomponents/PlaybooksGitRepositoryModal.tsx +++ b/client/src/pages/Admin/Settings/components/subcomponents/PlaybooksGitRepositoryModal.tsx @@ -1,5 +1,5 @@ import { SimpleIconsGit } from '@/components/Icons/CustomIcons'; -import CustomVaultsModal from '@/pages/Admin/Settings/components/subcomponents/CustomVaultsModal'; +import CustomVault from '@/pages/Admin/Settings/components/subcomponents/forms/CustomVault'; import DirectoryExclusionForm from '@/pages/Admin/Settings/components/subcomponents/forms/DirectoryExclusionForm'; import GitForm from '@/pages/Admin/Settings/components/subcomponents/forms/GitForm'; import { @@ -117,7 +117,6 @@ const PlaybooksGitRepositoryModal: React.FC< const editionMode = props.selectedRecord ? [ - ,