diff --git a/pkg/query/configuration/objectkind.go b/pkg/query/configuration/objectkind.go
index 3d0c1773d..493779090 100644
--- a/pkg/query/configuration/objectkind.go
+++ b/pkg/query/configuration/objectkind.go
@@ -183,7 +183,7 @@ var (
AddToSchemeFunc: rbacv1.AddToScheme,
}
- PolicyAgentEventObjectKind = ObjectKind{
+ PolicyAgentAuditEventObjectKind = ObjectKind{
Gvk: corev1.SchemeGroupVersion.WithKind("Event"),
NewClientObjectFunc: func() client.Object {
return &corev1.Event{}
@@ -195,7 +195,7 @@ var (
return false
}
- return e.Source.Component == "policy-agent"
+ return e.Labels["pac.weave.works/type"] == "Audit" && e.Source.Component == "policy-agent"
},
RetentionPolicy: RetentionPolicy(24 * time.Hour),
StatusFunc: func(obj client.Object) ObjectStatus {
@@ -231,7 +231,7 @@ var SupportedObjectKinds = []ObjectKind{
GitRepositoryObjectKind,
OCIRepositoryObjectKind,
BucketObjectKind,
- PolicyAgentEventObjectKind,
+ PolicyAgentAuditEventObjectKind,
}
// SupportedRbacKinds list with the default supported RBAC resources.
diff --git a/ui-cra/src/components/Clusters/index.tsx b/ui-cra/src/components/Clusters/index.tsx
index 42207ae0e..4182a51a7 100644
--- a/ui-cra/src/components/Clusters/index.tsx
+++ b/ui-cra/src/components/Clusters/index.tsx
@@ -9,19 +9,16 @@ import {
Kind,
KubeStatusIndicator,
Link,
- PolicyViolationsList,
- RouterTab,
- SubRouterTabs,
filterByStatusCallback,
filterConfig,
statusSortHelper,
- useListSources,
+ useListSources
} from '@weaveworks/weave-gitops';
import { Source } from '@weaveworks/weave-gitops/ui/lib/objects';
import { PageRoute } from '@weaveworks/weave-gitops/ui/lib/types';
import _ from 'lodash';
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';
-import { useHistory, useRouteMatch } from 'react-router-dom';
+import { useHistory } from 'react-router-dom';
import styled from 'styled-components';
import { GitProvider } from '../../api/gitauth/gitauth.pb';
import EKS from '../../assets/img/EKS.svg';
@@ -163,7 +160,6 @@ export const getGitRepos = (sources: Source[] | undefined) =>
const MCCP: FC<{
location: { state: { notification: NotificationData[] } };
}> = ({ location }) => {
- const { path } = useRouteMatch();
const { clusters, isLoading } = useClusters();
const { setNotifications } = useNotifications();
const [selectedCluster, setSelectedCluster] =
@@ -389,95 +385,85 @@ const MCCP: FC<{
)}
-
-
-
- (
-
- ),
- maxWidth: 25,
- },
- {
- label: 'Name',
- value: (c: GitopsClusterEnriched) =>
- c.controlPlane === true ? (
- {c.name}
- ) : (
-
- {c.name}
-
- ),
- sortValue: ({ name }) => name,
- textSearchable: true,
- maxWidth: 275,
- },
- {
- label: 'Dashboards',
- value: (c: GitopsClusterEnriched) => (
-
- ),
- },
- {
- label: 'Type',
- value: (c: GitopsClusterEnriched) => (
-
- ),
- },
- {
- label: 'Namespace',
- value: 'namespace',
- },
- {
- label: 'Status',
- value: (c: GitopsClusterEnriched) =>
- c.conditions && c.conditions.length > 0 ? (
-
- ) : null,
- sortValue: statusSortHelper,
- },
- {
- label: 'Message',
- value: (c: GitopsClusterEnriched) =>
- (c.conditions && c.conditions[0]?.message) || null,
- sortValue: ({ conditions }) => computeMessage(conditions),
- maxWidth: 600,
- },
- {
- label: '',
- value: (c: GitopsClusterEnriched) => (
-
- ),
- },
- ]}
- />
-
-
-
-
-
-
+
+ (
+
+ ),
+ maxWidth: 25,
+ },
+ {
+ label: 'Name',
+ value: (c: GitopsClusterEnriched) =>
+ c.controlPlane === true ? (
+ {c.name}
+ ) : (
+
+ {c.name}
+
+ ),
+ sortValue: ({ name }) => name,
+ textSearchable: true,
+ maxWidth: 275,
+ },
+ {
+ label: 'Dashboards',
+ value: (c: GitopsClusterEnriched) => (
+
+ ),
+ },
+ {
+ label: 'Type',
+ value: (c: GitopsClusterEnriched) => (
+
+ ),
+ },
+ {
+ label: 'Namespace',
+ value: 'namespace',
+ },
+ {
+ label: 'Status',
+ value: (c: GitopsClusterEnriched) =>
+ c.conditions && c.conditions.length > 0 ? (
+
+ ) : null,
+ sortValue: statusSortHelper,
+ },
+ {
+ label: 'Message',
+ value: (c: GitopsClusterEnriched) =>
+ (c.conditions && c.conditions[0]?.message) || null,
+ sortValue: ({ conditions }) => computeMessage(conditions),
+ maxWidth: 600,
+ },
+ {
+ label: '',
+ value: (c: GitopsClusterEnriched) => (
+
+ ),
+ },
+ ]}
+ />
+
diff --git a/ui-cra/src/components/Policies/Audit/AuditTable.tsx b/ui-cra/src/components/Policies/Audit/AuditTable.tsx
new file mode 100644
index 000000000..23b7699c5
--- /dev/null
+++ b/ui-cra/src/components/Policies/Audit/AuditTable.tsx
@@ -0,0 +1,192 @@
+import {
+ DataTable,
+ Flex,
+ Icon,
+ IconType,
+ Link,
+ RequestStateHandler,
+ Severity,
+ Text,
+ Timestamp,
+ V2Routes,
+ formatURL,
+} from '@weaveworks/weave-gitops';
+import { useListFacets } from '../../../hooks/query';
+import { QueryState, columnHeaderHandler } from '../../Explorer/hooks';
+
+import { IconButton } from '@material-ui/core';
+import _ from 'lodash';
+import qs from 'query-string';
+import { useEffect, useState } from 'react';
+import { useHistory } from 'react-router';
+import { QueryResponse } from '../../../api/query/query.pb';
+import { RequestError } from '../../../types/custom';
+import FilterDrawer from '../../Explorer/FilterDrawer';
+import Filters from '../../Explorer/Filters';
+import PaginationControls from '../../Explorer/PaginationControls';
+import QueryInput from '../../Explorer/QueryInput';
+import QueryStateChips from '../../Explorer/QueryStateChips';
+import { TableWrapper } from '../../Shared';
+
+type AuditProps = {
+ data: QueryResponse | undefined;
+ queryState: QueryState;
+ setQueryState: (queryState: QueryState) => void;
+};
+
+export const AuditTable = ({ data, queryState, setQueryState }: AuditProps) => {
+ const history = useHistory();
+ const [filterDrawerOpen, setFilterDrawerOpen] = useState(false);
+ const [showTable, setshowTable] = useState(false);
+
+ const { data: facetsRes, error, isLoading } = useListFacets();
+ const filteredFacets = facetsRes?.facets?.filter(f => f.field !== 'kind');
+ const rows = data?.objects?.map(obj => {
+ const { unstructured, cluster } = obj;
+ const details = JSON.parse(unstructured || '');
+ const {
+ metadata: {
+ annotations: { category, policy_name, severity, policy_id },
+ creationTimestamp,
+ },
+ involvedObject: { namespace, name, kind },
+ message,
+ } = details.Object;
+ return {
+ message,
+ cluster,
+ category,
+ policy_name,
+ severity,
+ creationTimestamp,
+ policy_id,
+ namespace,
+ name,
+ kind,
+ };
+ });
+
+ useEffect(() => {
+ const url = qs.parse(history.location.search);
+ const clearFilters = _.omit(url, ['filters', 'search']);
+ history.replace({
+ ...history.location,
+ search: qs.stringify(clearFilters),
+ });
+ setshowTable(true);
+ }, [history]);
+ return (
+
+
+
+
+ setFilterDrawerOpen(!filterDrawerOpen)}>
+
+
+
+
+
+ {showTable && (
+ (
+ {message}
+ ),
+ textSearchable: true,
+ maxWidth: 300,
+ sortValue: ({ message }) => message,
+ },
+ {
+ label: 'Cluster',
+ value: 'cluster',
+ sortValue: ({ cluster }) => cluster,
+ },
+ {
+ label: 'Application',
+ value: ({ namespace, name, kind }) =>
+ kind === 'Kustomization' || kind === 'HelmRelease'
+ ? `${namespace}/${name}`
+ : '-',
+ sortValue: ({ namespace, name }) => `${namespace}/${name}`,
+ maxWidth: 150,
+ },
+ {
+ label: 'Severity',
+ value: ({ severity }) => (
+
+ ),
+ sortValue: ({ severity }) => severity,
+ },
+ {
+ label: 'Category',
+ value: ({ category }) => (
+ {category}
+ ),
+ sortValue: ({ category }) => category,
+ maxWidth: 100,
+ },
+
+ {
+ label: 'Violated Policy',
+ value: ({ policy_name, cluster, policy_id }) => (
+
+
+ {policy_name}
+
+
+ ),
+ sortValue: ({ policy_name }) => policy_name,
+ maxWidth: 200,
+ },
+
+ {
+ label: 'Violation Time',
+ value: ({ creationTimestamp }) => (
+
+ ),
+ defaultSort: true,
+ sortValue: ({ creationTimestamp }) => {
+ const t =
+ creationTimestamp &&
+ new Date(creationTimestamp).getTime();
+ return t * -1;
+ },
+ },
+ ]}
+ hideSearchAndFilters
+ onColumnHeaderClick={columnHeaderHandler(
+ queryState,
+ setQueryState,
+ )}
+ />
+ )}
+
+ setFilterDrawerOpen(false)}
+ open={filterDrawerOpen}
+ >
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/ui-cra/src/components/Policies/Audit/PolicyAuditList.tsx b/ui-cra/src/components/Policies/Audit/PolicyAuditList.tsx
new file mode 100644
index 000000000..945918995
--- /dev/null
+++ b/ui-cra/src/components/Policies/Audit/PolicyAuditList.tsx
@@ -0,0 +1,46 @@
+import { RequestStateHandler, useFeatureFlags } from '@weaveworks/weave-gitops';
+import { useHistory } from 'react-router-dom';
+import { useQueryService } from '../../../hooks/query';
+import { RequestError } from '../../../types/custom';
+import { URLQueryStateManager } from '../../Explorer/QueryStateManager';
+import { QueryStateProvider } from '../../Explorer/hooks';
+import { AuditTable } from './AuditTable';
+import WarningMsg from './WarningMsg';
+
+const PolicyAuditList = () => {
+ const history = useHistory();
+ const { isFlagEnabled } = useFeatureFlags();
+ const useQueryServiceBackend = isFlagEnabled(
+ 'WEAVE_GITOPS_FEATURE_QUERY_SERVICE_BACKEND',
+ );
+ const manager = new URLQueryStateManager(history);
+ const queryState = manager.read();
+ const setQueryState = manager.write;
+ const { data, error, isLoading } = useQueryService({
+ terms: queryState.terms,
+ filters: ['kind:Event', ...queryState.filters],
+ limit: queryState.limit,
+ offset: queryState.offset,
+ orderBy: queryState.orderBy,
+ ascending: queryState.orderAscending,
+ });
+
+ return (
+
+
+ {useQueryServiceBackend ? (
+ data?.objects?.length && (
+
+ )
+ ) : (
+
+ )}
+
+
+ );
+};
+export default PolicyAuditList;
diff --git a/ui-cra/src/components/Policies/Audit/WarningMsg.tsx b/ui-cra/src/components/Policies/Audit/WarningMsg.tsx
new file mode 100644
index 000000000..f3973f8a7
--- /dev/null
+++ b/ui-cra/src/components/Policies/Audit/WarningMsg.tsx
@@ -0,0 +1,40 @@
+import {
+ Button,
+ Flex,
+ MessageBox,
+ Text
+} from '@weaveworks/weave-gitops';
+import { NotificationsWrapper } from '../../Layout/NotificationsWrapper';
+import { LinkTag } from '../../Shared';
+
+const WarningMsg = () => {
+ return (
+
+
+
+
+
+ Explorer Disabled
+
+
+ the explorer service is disabled and it's required to view the
+ audit logs.
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default WarningMsg;
diff --git a/ui-cra/src/components/Policies/PoliciesListPage.tsx b/ui-cra/src/components/Policies/PoliciesListPage.tsx
index e2aa2249e..0439bf205 100644
--- a/ui-cra/src/components/Policies/PoliciesListPage.tsx
+++ b/ui-cra/src/components/Policies/PoliciesListPage.tsx
@@ -1,20 +1,29 @@
-import { NotificationsWrapper } from '../Layout/NotificationsWrapper';
+import {
+ PolicyViolationsList,
+ RouterTab,
+ SubRouterTabs,
+} from '@weaveworks/weave-gitops';
+import { useRouteMatch } from 'react-router-dom';
import { Page } from '../Layout/App';
-import { useListPolicies } from '../../contexts/PolicyViolations';
-import { PolicyTable } from '@weaveworks/weave-gitops';
+import { PoliciesTab } from './PoliciesListTab';
+import PolicyAuditList from './Audit/PolicyAuditList';
const Policies = () => {
- const { data, isLoading } = useListPolicies({});
+ const { path } = useRouteMatch();
return (
-
-
- {data?.policies && (
-
- )}
-
+
+
+
+
+
+
+
+
+
+
+
+
);
};
diff --git a/ui-cra/src/components/Policies/PoliciesListTab.tsx b/ui-cra/src/components/Policies/PoliciesListTab.tsx
new file mode 100644
index 000000000..5b6ae7ae9
--- /dev/null
+++ b/ui-cra/src/components/Policies/PoliciesListTab.tsx
@@ -0,0 +1,18 @@
+import { PolicyTable } from '@weaveworks/weave-gitops';
+import { useListPolicies } from '../../contexts/PolicyViolations';
+import { TableWrapper } from '../Shared';
+import LoadingWrapper from '../Workspaces/WorkspaceDetails/Tabs/WorkspaceTabsWrapper';
+
+export const PoliciesTab = () => {
+ const { data, isLoading } = useListPolicies({});
+
+ return (
+
+ {data?.policies && (
+
+
+
+ )}
+
+ );
+};
diff --git a/ui-cra/src/components/Policies/PolicyViolationPage.tsx b/ui-cra/src/components/Policies/PolicyViolationPage.tsx
index 47dd65eb6..dd8aaafdd 100644
--- a/ui-cra/src/components/Policies/PolicyViolationPage.tsx
+++ b/ui-cra/src/components/Policies/PolicyViolationPage.tsx
@@ -17,7 +17,7 @@ const getPath = (kind?: string, violation?: PolicyValidation): Breadcrumb[] => {
if (!violation) return [{ label: '' }];
const { name, entity, namespace, clusterName, policyId } = violation;
if (!kind) {
- return [{ label: 'Clusters', url: `${Routes.Clusters}/violations` }];
+ return [{ label: 'Policies', url: `${Routes.Policies}/enforcement` }];
}
if (kind === Kind.Policy) {
@@ -65,7 +65,6 @@ const PolicyViolationPage = ({ id, name, clusterName, kind }: Props) => {
const entityObject = new FluxObject({
payload: violation?.violatingEntity,
});
-
return (
{
const errorCount = screen.queryByTestId('errorsCount');
expect(errorCount?.textContent).toEqual('2');
});
- it('renders a list of policies', async () => {
- const filterTable = new TestFilterableTable('policy-list', fireEvent);
+ it('renders Tabs of policies Page', async () => {
api.ListPoliciesReturns = listPoliciesResponse;
await act(async () => {
const c = wrap();
render(c);
});
- expect(await screen.findByText('Policies')).toBeTruthy();
+ const tabs = await screen.getAllByRole('tab');
+ expect(await screen.getByTestId('text-Policies')).toHaveTextContent(
+ 'Policies',
+ );
+ expect(tabs).toHaveLength(3);
+ expect(tabs[0]).toHaveTextContent('Policies');
+ expect(tabs[1]).toHaveTextContent('Policy Audit');
+ expect(tabs[2]).toHaveTextContent('Enforcement Events');
+ });
+
+ it('renders a list of policies', async () => {
+ const filterTable = new TestFilterableTable('policy-list', fireEvent);
+ api.ListPoliciesReturns = listPoliciesResponse;
+ await act(async () => {
+ const c = wrap();
+ render(c);
+ });
filterTable.testRenderTable(
[
diff --git a/ui-cra/src/components/Workspaces/WorkspaceDetails/Tabs/WorkspaceTabsWrapper.tsx b/ui-cra/src/components/Workspaces/WorkspaceDetails/Tabs/WorkspaceTabsWrapper.tsx
index dc2cd286a..7f1c10c70 100644
--- a/ui-cra/src/components/Workspaces/WorkspaceDetails/Tabs/WorkspaceTabsWrapper.tsx
+++ b/ui-cra/src/components/Workspaces/WorkspaceDetails/Tabs/WorkspaceTabsWrapper.tsx
@@ -19,11 +19,13 @@ const LoadingWrapper: FC = ({
return (
{loading && (
-
-
-
-
-
+
+
+
+
+
+
+
)}
{(errors?.length || errorMessage) && (
diff --git a/ui-cra/src/routes.tsx b/ui-cra/src/routes.tsx
index 2ddfa8b91..849fb1eaf 100644
--- a/ui-cra/src/routes.tsx
+++ b/ui-cra/src/routes.tsx
@@ -218,7 +218,7 @@ const AppRoutes = () => {
path={V2Routes.ImagePolicyDetails}
component={withSearchParams(ImagePolicyDetails)}
/>
-
+