diff --git a/locales/en/plugin__console-plugin-template.json b/locales/en/plugin__console-plugin-template.json index 935a6df..69e0831 100644 --- a/locales/en/plugin__console-plugin-template.json +++ b/locales/en/plugin__console-plugin-template.json @@ -17,5 +17,13 @@ "Address": "Address", "Dashboard": "Dashboard", "Policies": "Policies", - "Policy Topology": "Policy Topology" + "Policy Topology": "Policy Topology", + "TLS": "TLS", + "Are you sure you want to delete the policy": "Are you sure you want to delete the policy", + "All Policies": "All Policies", + "DNS": "DNS", + "Auth": "Auth", + "RateLimit": "RateLimit", + "Edit": "Edit", + "Delete": "Delete" } diff --git a/src/components/KuadrantPoliciesPage.tsx b/src/components/KuadrantPoliciesPage.tsx index b2180f0..da80c00 100644 --- a/src/components/KuadrantPoliciesPage.tsx +++ b/src/components/KuadrantPoliciesPage.tsx @@ -1,10 +1,31 @@ import * as React from 'react'; import { useParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; -import { Dropdown, DropdownItem, DropdownList, MenuToggle, MenuToggleElement, Alert, AlertGroup, Title} from '@patternfly/react-core'; +import { + Dropdown, + DropdownItem, + DropdownList, + MenuToggle, + MenuToggleElement, + Alert, + AlertGroup, + Title, + Button, + ButtonVariant, +} from '@patternfly/react-core'; +import { K8sModel } from '@openshift-console/dynamic-plugin-sdk/lib/api/common-types'; import { sortable } from '@patternfly/react-table'; -import EllipsisVIcon from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon'; -import { useK8sWatchResource, K8sResourceCommon, ResourceLink, useActiveNamespace, HorizontalNav, useListPageFilter, +import { EllipsisVIcon } from '@patternfly/react-icons'; +import { Modal, ModalBody, ModalFooter, ModalHeader } from '@patternfly/react-core/next'; + +import { + k8sDelete, + useK8sWatchResource, + K8sResourceCommon, + ResourceLink, + useActiveNamespace, + HorizontalNav, + useListPageFilter, ListPageBody, ListPageCreate, ListPageFilter, @@ -15,9 +36,10 @@ import { useK8sWatchResource, K8sResourceCommon, ResourceLink, useActiveNamespac NamespaceBar, Timestamp, useActivePerspective, - } from '@openshift-console/dynamic-plugin-sdk'; +} from '@openshift-console/dynamic-plugin-sdk'; import './kuadrant.css'; + interface Resource { name: string; gvk: { @@ -40,7 +62,7 @@ const statusConditionsAsString = (obj: any) => { return obj.status.conditions .map(condition => `${condition.type}=${condition.status}`) .join(','); -} +}; const resources: Resource[] = [ { name: 'AuthPolicies', gvk: { group: 'kuadrant.io', version: 'v1beta2', kind: 'AuthPolicy' } }, @@ -56,6 +78,18 @@ type AllPoliciesTableProps = { loadError: any; }; +type DropdownWithKebabProps = { + obj: K8sResourceCommon; +}; + +type PoliciesTableProps = { + data: K8sResourceCommon[]; + unfilteredData: K8sResourceCommon[]; + loaded: boolean; + loadError: any; + resource: Resource; +}; + const AllPoliciesTable: React.FC = ({ data, unfilteredData, loaded, loadError }) => { const { t } = useTranslation(); @@ -100,7 +134,11 @@ const AllPoliciesTable: React.FC = ({ data, unfilteredDat return ( <> - + {obj.kind} @@ -129,58 +167,114 @@ const AllPoliciesTable: React.FC = ({ data, unfilteredDat ); }; -type DropdownWithKebabProps = { - obj: K8sResourceCommon; -}; - const DropdownWithKebab: React.FC = ({ obj }) => { + const { t } = useTranslation('plugin__console-plugin-template'); const [isOpen, setIsOpen] = React.useState(false); + const [isDeleteModalOpen, setIsDeleteModalOpen] = React.useState(false); const onToggleClick = () => { setIsOpen(!isOpen); }; - const onSelect = (_event: React.MouseEvent | undefined, value: string | number | undefined) => { - // eslint-disable-next-line no-console - console.log('selected', value); + const getModelFromResource = (obj: K8sResourceCommon): K8sModel => { + const pluralizeKind = (kind: string) => { + if (kind.endsWith('y')) { + return `${kind.slice(0, -1)}ies`.toLowerCase(); + } + return `${kind.toLowerCase()}s`; + }; + + return { + apiGroup: obj.apiVersion.split('/')[0], + apiVersion: obj.apiVersion.split('/')[1], + kind: obj.kind, + plural: pluralizeKind(obj.kind), + namespaced: !!obj.metadata.namespace, + abbr: obj.kind.charAt(0), + label: obj.kind, + labelPlural: pluralizeKind(obj.kind), + }; + }; + + const onDeleteConfirm = async () => { + try { + const model = getModelFromResource(obj); + await k8sDelete({ model, resource: obj }); + console.log('Successfully deleted', obj.metadata.name); + } catch (error) { + console.error('Failed to delete', obj.metadata.name, error); + } finally { + setIsDeleteModalOpen(false); + } + }; + + const onDeleteClick = () => { + setIsDeleteModalOpen(true); + }; + + const onSelect = ( + _event: React.MouseEvent | undefined, + value: string | number | undefined + ) => { setIsOpen(false); + if (value === 'delete') { + onDeleteClick(); + } }; return ( - setIsOpen(isOpen)} - toggle={(toggleRef: React.Ref) => ( - - - - )} - shouldFocusToggleOnSelect - > - - - Edit - - - + <> + setIsOpen(isOpen)} + toggle={(toggleRef: React.Ref) => ( + + + + )} + shouldFocusToggleOnSelect + > + + + {t('Edit')} + + + {t('Delete')} + + + + + setIsDeleteModalOpen(false)} + aria-labelledby="delete-modal-title" + aria-describedby="delete-modal-body" + variant="medium" + > + + + {t("Are you sure you want to delete the policy")}: {obj.metadata.name}? + + + + + + + ); }; -type PoliciesTableProps = { - data: K8sResourceCommon[]; - unfilteredData: K8sResourceCommon[]; - loaded: boolean; - loadError: any; - resource: any; -}; - const PoliciesTable: React.FC = ({ data, unfilteredData, loaded, loadError, resource }) => { const { t } = useTranslation(); @@ -246,62 +340,38 @@ const PoliciesTable: React.FC = ({ data, unfilteredData, loa ); }; -const AllPoliciesListPage = (activeNamespace: string) => { - const [policies, setPolicies] = React.useState([]); - const [loaded, setLoaded] = React.useState(false); - const [loadError, setLoadError] = React.useState(false); - - resources.forEach((resource) => { +const AllPoliciesListPage: React.FC<{ activeNamespace: string }> = ({ activeNamespace }) => { + const watchedResources = resources.map((resource) => { const { group, version, kind } = resource.gvk; - const [res_policies, res_loaded, res_loadError] = useK8sWatchResource({ + return useK8sWatchResource({ groupVersionKind: { group, version, kind }, namespace: activeNamespace === '#ALL_NS#' ? undefined : activeNamespace, isList: true, }); - - React.useEffect(() => { - if (res_loaded) { - setPolicies((prevPolicies) => { - const newPolicies = res_policies.filter((newPolicy) => - !prevPolicies.some((prevPolicy) => prevPolicy.metadata.uid === newPolicy.metadata.uid) - ); - return [...prevPolicies, ...newPolicies]; - }); - setLoaded(true); - } - if (res_loadError) { - setLoadError(true); - } - }, [res_policies, res_loaded, res_loadError]); }); + const policies = watchedResources.flatMap(([res_policies]) => res_policies || []); + const loaded = watchedResources.every(([_, res_loaded]) => res_loaded); + const loadError = watchedResources.some(([_, __, res_loadError]) => res_loadError); + const [data, filteredData, onFilterChange] = useListPageFilter(policies); return ( <> - + ... - - + + ); }; -const PoliciesListPage = (resource: Resource, activeNamespace: string) => { +const PoliciesListPage: React.FC<{ resource: Resource; activeNamespace: string }> = ({ resource, activeNamespace }) => { const { group, version, kind } = resource.gvk; const [policies, loaded, loadError] = useK8sWatchResource({ groupVersionKind: { group, version, kind }, @@ -315,18 +385,16 @@ const PoliciesListPage = (resource: Resource, activeNamespace: string) => { return ( <> - + ... -
- - {t(`plugin__console-plugin-template~Create ${resource.gvk.kind}`)} +
+ + + {t(`plugin__console-plugin-template~Create ${resource.gvk.kind}`)} +
{ const { t } = useTranslation('plugin__console-plugin-template'); const { ns } = useParams<{ ns: string }>(); const [activeNamespace, setActiveNamespace] = useActiveNamespace(); - const [activePerspective, _] = useActivePerspective(); - console.log(`Active perspective: ${activePerspective}`); + const [activePerspective] = useActivePerspective(); React.useEffect(() => { if (ns && ns !== activeNamespace) { setActiveNamespace(ns); } - console.log(`Initial namespace: ${activeNamespace}`); }, [ns, activeNamespace, setActiveNamespace]); - const All: React.FC = () => { - return AllPoliciesListPage(activeNamespace) - }; - - const Auth: React.FC = () => { - return PoliciesListPage(resources[0], activeNamespace) - }; + const All: React.FC = () => ; + const Auth: React.FC = () => ; + const RateLimit: React.FC = () => ; - const RateLimit: React.FC = () => { - return PoliciesListPage(resources[2], activeNamespace) - }; - let pages = [ { href: '', - name: 'All Policies', + name: t('All Policies'), component: All - }, + } ]; if (activePerspective === 'admin') { - const DNS: React.FC = () => { - return PoliciesListPage(resources[1], activeNamespace); - }; - - const TLS: React.FC = () => { - return PoliciesListPage(resources[3], activeNamespace); - }; + const DNS: React.FC = () => ; + const TLS: React.FC = () => ; pages = [ ...pages, { href: 'dns', - name: 'DNS', + name: t('DNS'), component: DNS }, { href: 'tls', - name: 'TLS', + name: t('TLS'), component: TLS - }, + } ]; } pages = [ ...pages, { href: 'auth', - name: 'Auth', + name: t('Auth'), component: Auth }, { href: 'ratelimit', - name: 'RateLimit', + name: t('RateLimit'), component: RateLimit } ]; return ( <> - - {t('Kuadrant')} + + + {t('Kuadrant')} + );