From 927cf00cc6f85f8ed3fc95c6a8b5417ff367e603 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Sat, 9 Sep 2023 00:41:45 +0200 Subject: [PATCH 01/25] remove duplicate --- src/components/utilities/CippFuzzySearch.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/utilities/CippFuzzySearch.js b/src/components/utilities/CippFuzzySearch.js index a97625dcd818..43021b80014e 100644 --- a/src/components/utilities/CippFuzzySearch.js +++ b/src/components/utilities/CippFuzzySearch.js @@ -13,7 +13,6 @@ function CippfuzzySearch(options) { useExtendedSearch: true, includeMatches: true, includeScore: true, - useExtendedSearch: true, }) return function (value) { if (!value.length) { From f529c982149ec35e0271b7f63f1709e4462b733a Mon Sep 17 00:00:00 2001 From: lwhitelock <79275328+lwhitelock@users.noreply.github.com> Date: Sat, 9 Sep 2023 10:07:17 +0100 Subject: [PATCH 02/25] Refactor to support new extensions in the future This refactors the frontend code to allow support for additional extension mappings in the future. --- src/views/cipp/CIPPSettings.js | 37 ++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/src/views/cipp/CIPPSettings.js b/src/views/cipp/CIPPSettings.js index 09d06f708953..9f72a84401e9 100644 --- a/src/views/cipp/CIPPSettings.js +++ b/src/views/cipp/CIPPSettings.js @@ -1576,12 +1576,12 @@ const ExtensionsTab = () => { } const MappingsTab = () => { - const [listBackend, listBackendResult] = useLazyGenericGetRequestQuery() - const [setExtensionconfig, extensionConfigResult] = useLazyGenericPostRequestQuery() + const [listHaloBackend, listBackendHaloResult] = useLazyGenericGetRequestQuery() + const [setHaloExtensionconfig, extensionHaloConfigResult] = useLazyGenericPostRequestQuery() - const onSubmit = (values) => { - setExtensionconfig({ - path: 'api/ExecExtensionMapping?AddMapping=true', + const onHaloSubmit = (values) => { + setHaloExtensionconfig({ + path: 'api/ExecExtensionMapping?AddMapping=Halo', values: { mappings: values }, }) } @@ -1590,44 +1590,47 @@ const MappingsTab = () => { {listBackendResult.isUninitialized && listBackend({ path: 'api/ExecExtensionMapping?List=true' })} <> - + HaloPSA Mapping Table - {listBackendResult.isFetching ? ( + {listBackendHaloResult.isFetching ? ( ) : (
{ return ( Use the table below to map your client to the correct PSA client - {listBackendResult.isSuccess && - listBackendResult.data.Tenants.map((tenant) => ( + {listBackendHaloResult.isSuccess && + listBackendHaloResult.data.Tenants.map((tenant) => ( ))} - {extensionConfigResult.isFetching && ( + {extensionHaloConfigResult.isFetching && ( )} Set Mappings - {(extensionConfigResult.isSuccess || extensionConfigResult.isError) && ( - - {extensionConfigResult.isSuccess - ? extensionConfigResult.data.Results + {(extensionHaloConfigResult.isSuccess || + extensionHaloConfigResult.isError) && ( + + {extensionHaloConfigResult.isSuccess + ? extensionHaloConfigResult.data.Results : 'Error'} )} From 2812f32d6cb317d31949b63268bcf0a5b194bf17 Mon Sep 17 00:00:00 2001 From: rvdwegen Date: Tue, 12 Sep 2023 23:48:26 +0200 Subject: [PATCH 03/25] Make user count cards on dashboard interactable --- src/views/home/Home.js | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/views/home/Home.js b/src/views/home/Home.js index e3025e587fda..10cf1a03c502 100644 --- a/src/views/home/Home.js +++ b/src/views/home/Home.js @@ -143,6 +143,10 @@ const Home = () => { +
{issuccessUserCounts && !isFetchingUserCount ? dashboard?.Users : }
@@ -150,6 +154,14 @@ const Home = () => {
+
{issuccessUserCounts && !isFetchingUserCount ? dashboard?.LicUsers : }
@@ -157,6 +169,15 @@ const Home = () => {
+
{issuccessUserCounts && !isFetchingUserCount ? dashboard?.Gas : }
@@ -164,6 +185,14 @@ const Home = () => {
+
{issuccessUserCounts && !isFetchingUserCount ? dashboard?.Guests : }
From 53c9fd2ca068de94fbe0da92d48045256f3cea5e Mon Sep 17 00:00:00 2001 From: lwhitelock <79275328+lwhitelock@users.noreply.github.com> Date: Thu, 14 Sep 2023 08:06:55 +0100 Subject: [PATCH 04/25] Fix load Halo Backend Result Fixed renaming a function that was missed. --- src/views/cipp/CIPPSettings.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/views/cipp/CIPPSettings.js b/src/views/cipp/CIPPSettings.js index 9f72a84401e9..fe54e4805cc2 100644 --- a/src/views/cipp/CIPPSettings.js +++ b/src/views/cipp/CIPPSettings.js @@ -1587,8 +1587,8 @@ const MappingsTab = () => { } return (
- {listBackendResult.isUninitialized && - listBackend({ path: 'api/ExecExtensionMapping?List=true' })} + {listBackendHaloResult.isUninitialized && + listHaloBackend({ path: 'api/ExecExtensionMapping?List=Halo' })} <> From 2c19f8cab209e755a9feee8571e87bde6d6ba5d2 Mon Sep 17 00:00:00 2001 From: BNWEIN Date: Wed, 20 Sep 2023 12:42:30 +0100 Subject: [PATCH 05/25] Added Username to top of the BEC page BEC Page did not show what user it was running on. Added that information --- src/views/identity/administration/Users.js | 2 +- src/views/identity/administration/ViewBEC.js | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/views/identity/administration/Users.js b/src/views/identity/administration/Users.js index 4f64168fa419..928853445e6a 100644 --- a/src/views/identity/administration/Users.js +++ b/src/views/identity/administration/Users.js @@ -77,7 +77,7 @@ const Offcanvas = (row, rowIndex, formatExtraData) => { }, { label: 'Research Compromised Account', - link: `/identity/administration/ViewBec?userId=${row.id}&tenantDomain=${tenant.defaultDomainName}`, + link: `/identity/administration/ViewBec?userId=${row.id}&tenantDomain=${tenant.defaultDomainName}&ID=${row.userPrincipalName}`, color: 'info', }, { diff --git a/src/views/identity/administration/ViewBEC.js b/src/views/identity/administration/ViewBEC.js index d97e5639ae5a..bfe147e850cb 100644 --- a/src/views/identity/administration/ViewBEC.js +++ b/src/views/identity/administration/ViewBEC.js @@ -1,5 +1,5 @@ import React, { useEffect } from 'react' -import { CButton, CCallout, CLink } from '@coreui/react' +import { CButton, CCallout, CLink, CCardTitle } from '@coreui/react' import { CCardBody, CSpinner } from '@coreui/react' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { @@ -27,6 +27,7 @@ import useConfirmModal from 'src/hooks/useConfirmModal' const ViewBec = () => { let query = useQuery() const userId = query.get('userId') + const userName = query.get('ID') const tenantDomain = query.get('tenantDomain') const [execBecRemediate, execRemediateResults] = useLazyGenericPostRequestQuery() const [execBecView, results] = useLazyExecBecCheckQuery() @@ -232,7 +233,7 @@ const ViewBec = () => { Business Email Compromise Overview - {userName}} button={ Date: Thu, 21 Sep 2023 20:38:57 +0200 Subject: [PATCH 06/25] Fixed action menu API calls --- src/views/identity/administration/Groups.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/views/identity/administration/Groups.js b/src/views/identity/administration/Groups.js index 49400c7206d9..5f29ef909853 100644 --- a/src/views/identity/administration/Groups.js +++ b/src/views/identity/administration/Groups.js @@ -161,7 +161,7 @@ const Groups = () => { label: 'Hide from Global Address List', color: 'info', modal: true, - modalUrl: `/api/ExecGroupsHideFromGAL?TenantFilter=${tenant.defaultDomainName}&ID=!mail&GroupType=!calculatedGroupType&HidefromGAL=true`, + modalUrl: `/api/ExecGroupsHideFromGAL?TenantFilter=${tenant.defaultDomainName}&ID=!id&GroupType=!calculatedGroupType&HidefromGAL=true`, modalMessage: 'Are you sure you want to hide this mailbox from the global address list? Remember this will not work if the group is AD Synched.', }, @@ -169,7 +169,7 @@ const Groups = () => { label: 'Unhide from Global Address List', color: 'info', modal: true, - modalUrl: `/api/ExecGroupsHideFromGAL?TenantFilter=${tenant.defaultDomainName}&ID=!mail&GroupType=!calculatedGroupType`, + modalUrl: `/api/ExecGroupsHideFromGAL?TenantFilter=${tenant.defaultDomainName}&ID=!id&GroupType=!calculatedGroupType`, modalMessage: 'Are you sure you want to unhide this mailbox from the global address list? Remember this will not work if the group is AD Synched.', }, @@ -177,7 +177,7 @@ const Groups = () => { label: 'Only allow messages from people inside the organisation', color: 'info', modal: true, - modalUrl: `/api/ExecGroupsDeliveryManagement?TenantFilter=${tenant.defaultDomainName}&ID=!mail&GroupType=!calculatedGroupType&OnlyAllowInternal=true`, + modalUrl: `/api/ExecGroupsDeliveryManagement?TenantFilter=${tenant.defaultDomainName}&ID=!id&GroupType=!calculatedGroupType&OnlyAllowInternal=true`, modalMessage: 'Are you sure you want to only allow messages from people inside the organisation? Remember this will not work if the group is AD Synched.', }, @@ -185,7 +185,7 @@ const Groups = () => { label: 'Allow messages from people inside and outside the organisation', color: 'info', modal: true, - modalUrl: `/api/ExecGroupsDeliveryManagement?TenantFilter=${tenant.defaultDomainName}&ID=!mail&GroupType=!calculatedGroupType`, + modalUrl: `/api/ExecGroupsDeliveryManagement?TenantFilter=${tenant.defaultDomainName}&ID=!id&GroupType=!calculatedGroupType`, modalMessage: 'Are you sure you want to allow messages from people inside and outside the organisation? Remember this will not work if the group is AD Synched.', }, @@ -193,7 +193,7 @@ const Groups = () => { label: 'Delete Group', color: 'warning', modal: true, - modalUrl: `/api/ExecGroupsDelete?TenantFilter=${tenant.defaultDomainName}&ID=!mail&GroupType=!calculatedGroupType&DisplayName=!displayName`, + modalUrl: `/api/ExecGroupsDelete?TenantFilter=${tenant.defaultDomainName}&ID=!id&GroupType=!calculatedGroupType&DisplayName=!displayName`, modalMessage: 'Are you sure you want to delete this group.', }, ], From e3c8737a53ddd746a674650d01f1730d4d442d3b Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Thu, 21 Sep 2023 20:51:49 +0200 Subject: [PATCH 07/25] added automatic help to each page --- src/components/layout/AppHeader.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/components/layout/AppHeader.js b/src/components/layout/AppHeader.js index bfda84ceee1b..0bc158732883 100644 --- a/src/components/layout/AppHeader.js +++ b/src/components/layout/AppHeader.js @@ -11,6 +11,7 @@ import { CHeaderToggler, CImage, CSidebarBrand, + CButton, } from '@coreui/react' import { AppHeaderDropdown, AppHeaderSearch } from 'src/components/header' import { TenantSelector } from '../utilities' @@ -22,8 +23,12 @@ import { faCaretSquareLeft, faCaretSquareRight } from '@fortawesome/free-solid-s import { toggleSidebarShow } from 'src/store/features/app' import { useMediaPredicate } from 'react-media-hook' import { useLoadAlertsDashQuery } from 'src/store/api/app' +import { Link } from 'react-router-dom' +import { useLocation } from 'react-router-dom' + const AppHeader = () => { const dispatch = useDispatch() + const location = useLocation() const sidebarShow = useSelector((state) => state.app.sidebarShow) const currentTheme = useSelector((state) => state.app.currentTheme) const preferredTheme = useMediaPredicate('(prefers-color-scheme: dark)') ? 'impact' : 'cyberdrain' @@ -57,6 +62,16 @@ const AppHeader = () => { + + + + + + + From 1aa388b8c649bc6ecf2f269fbe9bba05c73685e5 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Thu, 21 Sep 2023 22:52:56 +0200 Subject: [PATCH 08/25] fixed mailbox permissions overview --- .../administration/EditMailboxPermissions.js | 952 +++++++++--------- 1 file changed, 459 insertions(+), 493 deletions(-) diff --git a/src/views/email-exchange/administration/EditMailboxPermissions.js b/src/views/email-exchange/administration/EditMailboxPermissions.js index 2841cf1c4d41..b0649954994b 100644 --- a/src/views/email-exchange/administration/EditMailboxPermissions.js +++ b/src/views/email-exchange/administration/EditMailboxPermissions.js @@ -16,7 +16,6 @@ import { CSpinner, } from '@coreui/react' import useQuery from 'src/hooks/useQuery' -import { CippPage, CippPageList, CippMasonry, CippMasonryItem } from 'src/components/layout' import { useDispatch } from 'react-redux' import { Form, Field } from 'react-final-form' import { RFFSelectSearch, RFFCFormSelect, RFFCFormCheck, RFFCFormInput } from 'src/components/forms' @@ -38,38 +37,131 @@ const formatter = (cell, warning = false, reverse = false, colourless = false) = CellBoolean({ cell, warning, reverse, colourless }) const MailboxSettings = () => { + const dispatch = useDispatch() + let query = useQuery() + const userId = query.get('userId') + const tenantDomain = query.get('tenantDomain') const [active, setActive] = useState(1) + const { + data: usercal = {}, + isFetching: usercalIsFetching, + error: usercalError, + } = useListCalendarPermissionsQuery({ tenantDomain, userId }) + const columnsCal = [ + { + name: 'User', + selector: (row) => row['User'], + sortable: true, + wrap: true, + cell: (row) => row['User'], + exportSelector: 'User', + maxWidth: '150px', + }, + { + name: 'AccessRights', + selector: (row) => row['AccessRights'], + sortable: true, + wrap: true, + cell: (row) => row['AccessRights'], + exportSelector: 'AccessRights', + maxWidth: '150px', + }, + { + name: 'Identity', + selector: (row) => row['Identity'], + sortable: true, + wrap: true, + cell: (row) => row['Identity'], + exportSelector: 'Identity', + maxWidth: '150px', + }, + ] + const columns = [ + { + name: 'User', + selector: (row) => row.User, + sortable: true, + wrap: true, + exportSelector: 'User', + }, + { + name: 'Permissions', + selector: (row) => row['Permissions'], + sortable: true, + wrap: true, + exportSelector: 'Permissions', + }, + ] + + const { + data: user = {}, + isFetching: userIsFetching, + error: userError, + } = useListMailboxPermissionsQuery({ tenantDomain, userId }) + return ( - - - - - setActive(1)} href="#"> - Mailbox Permissions - - setActive(2)} href="#"> - Calendar Permissions - - setActive(3)} href="#"> - Mailbox Forwarding - - - - - - - - - - - - - - - - - - + + + + + + setActive(1)} href="#"> + Mailbox Permissions + + setActive(2)} href="#"> + Calendar Permissions + + setActive(3)} href="#"> + Mailbox Forwarding + + + + + + + + + + + + + + + + + + + + + + Account Information - {userId} + + + {active === 1 && ( + <> + {userIsFetching && } + {!userIsFetching && ( + + )} + + )} + {active === 2 && ( + <> + {usercalIsFetching && } + {!usercalIsFetching && ( + + )} + + )} + {active === 3 && ( + <> + + + )} + + + + ) } @@ -86,13 +178,6 @@ const MailboxPermissions = () => { //const [EditMailboxPermission, { error: EditMailboxPermissionError, isFetching: EditMailboxPermissionIsFetching }] = useEditMailboxPermissionMutation() const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery() - const { - data: user = {}, - isFetching: userIsFetching, - error: userError, - refetch: refetchPermissions, - } = useListMailboxPermissionsQuery({ tenantDomain, userId }) - const { data: users = [], isFetching: usersIsFetching, @@ -100,9 +185,6 @@ const MailboxPermissions = () => { } = useListUsersQuery({ tenantDomain }) useEffect(() => { - if (postResults.isSuccess) { - refetchPermissions() - } if (!userId || !tenantDomain) { ModalService.open({ body: 'Error invalid request, could not load requested user.', @@ -112,7 +194,7 @@ const MailboxPermissions = () => { } else { setQueryError(false) } - }, [userId, tenantDomain, dispatch, postResults, refetchPermissions]) + }, [userId, tenantDomain, dispatch, postResults]) const onSubmit = (values) => { const shippedValues = { userid: userId, @@ -128,36 +210,16 @@ const MailboxPermissions = () => { //window.alert(JSON.stringify(shippedValues)) genericPostRequest({ path: '/api/ExecEditMailboxPermissions', values: shippedValues }) } - const initialState = { - ...user, - } - - const columns = [ - { - name: 'User', - selector: (row) => row.User, - sortable: true, - wrap: true, - exportSelector: 'User', - }, - { - name: 'Permissions', - selector: (row) => row['Permissions'], - sortable: true, - wrap: true, - exportSelector: 'Permissions', - }, - ] const formDisabled = queryError === true return ( - + <> {!queryError && ( <> {queryError && ( - + {/* @todo add more descriptive help message here */} Failed to load user @@ -166,203 +228,147 @@ const MailboxPermissions = () => { )} - - - - Account Details - {userId} - - - {usersIsFetching && } - {userError && Error loading user} - {!usersIsFetching && ( - { - return ( - - - - ({ - value: user.mail, - name: `${user.displayName} - ${user.mail} `, - }))} - placeholder={!usersIsFetching ? 'Select user' : 'Loading...'} - name="RemoveFullAccess" - /> - {usersError && Failed to load list of users} - - - ({ - value: user.mail, - name: `${user.displayName} - ${user.mail} `, - }))} - placeholder={!usersIsFetching ? 'Select user' : 'Loading...'} - name="AddFullAccess" - /> - {usersError && Failed to load list of users} - - - ({ - value: user.mail, - name: `${user.displayName} - ${user.mail} `, - }))} - placeholder={!usersIsFetching ? 'Select user' : 'Loading...'} - name="AddFullAccessNoAutoMap" - /> - {usersError && Failed to load list of users} - - - ({ - value: user.mail, - name: `${user.displayName} - ${user.mail} `, - }))} - placeholder={!usersIsFetching ? 'Select user' : 'Loading...'} - name="AddSendAs" - /> - {usersError && Failed to load list of users} - - - ({ - value: user.mail, - name: `${user.displayName} - ${user.mail} `, - }))} - placeholder={!usersIsFetching ? 'Select user' : 'Loading...'} - name="RemoveSendAs" - /> - {usersError && Failed to load list of users} - - - ({ - value: user.mail, - name: `${user.displayName} - ${user.mail} `, - }))} - placeholder={!usersIsFetching ? 'Select user' : 'Loading...'} - name="AddSendOnBehalf" - /> - {usersError && Failed to load list of users} - - - ({ - value: user.mail, - name: `${user.displayName} - ${user.mail} `, - }))} - placeholder={!usersIsFetching ? 'Select user' : 'Loading...'} - name="RemoveSendOnBehalf" - /> - {usersError && Failed to load list of users} - - - - - - Edit User Permissions - {postResults.isFetching && ( - - )} - - - - {postResults.isSuccess && ( - - {postResults.data.Results.map((result, idx) => ( -
  • {result}
  • - ))} -
    + {usersIsFetching && } + {!usersIsFetching && ( + { + return ( + + + + ({ + value: user.mail, + name: `${user.displayName} - ${user.mail} `, + }))} + placeholder={!usersIsFetching ? 'Select user' : 'Loading...'} + name="RemoveFullAccess" + /> + {usersError && Failed to load list of users} + + + ({ + value: user.mail, + name: `${user.displayName} - ${user.mail} `, + }))} + placeholder={!usersIsFetching ? 'Select user' : 'Loading...'} + name="AddFullAccess" + /> + {usersError && Failed to load list of users} + + + ({ + value: user.mail, + name: `${user.displayName} - ${user.mail} `, + }))} + placeholder={!usersIsFetching ? 'Select user' : 'Loading...'} + name="AddFullAccessNoAutoMap" + /> + {usersError && Failed to load list of users} + + + ({ + value: user.mail, + name: `${user.displayName} - ${user.mail} `, + }))} + placeholder={!usersIsFetching ? 'Select user' : 'Loading...'} + name="AddSendAs" + /> + {usersError && Failed to load list of users} + + + ({ + value: user.mail, + name: `${user.displayName} - ${user.mail} `, + }))} + placeholder={!usersIsFetching ? 'Select user' : 'Loading...'} + name="RemoveSendAs" + /> + {usersError && Failed to load list of users} + + + ({ + value: user.mail, + name: `${user.displayName} - ${user.mail} `, + }))} + placeholder={!usersIsFetching ? 'Select user' : 'Loading...'} + name="AddSendOnBehalf" + /> + {usersError && Failed to load list of users} + + + ({ + value: user.mail, + name: `${user.displayName} - ${user.mail} `, + }))} + placeholder={!usersIsFetching ? 'Select user' : 'Loading...'} + name="RemoveSendOnBehalf" + /> + {usersError && Failed to load list of users} + + + + + + Edit User Permissions + {postResults.isFetching && ( + )} - - ) - }} - /> - )} -
    -
    -
    - - - - Account Information - - - {userIsFetching && } - {!userIsFetching && ( - <> - - - )} - - - +
    + + + {postResults.isSuccess && ( + + {postResults.data.Results.map((result, idx) => ( +
  • {result}
  • + ))} +
    + )} + + ) + }} + /> + )} )} -
    + ) } -const columns = [ - { - name: 'User', - selector: (row) => row['User'], - sortable: true, - wrap: true, - cell: (row) => row['User'], - exportSelector: 'User', - maxWidth: '150px', - }, - { - name: 'AccessRights', - selector: (row) => row['AccessRights'], - sortable: true, - wrap: true, - cell: (row) => row['AccessRights'], - exportSelector: 'AccessRights', - maxWidth: '150px', - }, - { - name: 'Identity', - selector: (row) => row['Identity'], - sortable: true, - wrap: true, - cell: (row) => row['Identity'], - exportSelector: 'Identity', - maxWidth: '150px', - }, -] - const CalendarPermissions = () => { const dispatch = useDispatch() let query = useQuery() @@ -419,7 +425,7 @@ const CalendarPermissions = () => { UsersMapped.unshift({ value: 'Default', name: 'Default' }) return ( - + <> {!queryError && ( <> {postResults.isSuccess && ( @@ -436,109 +442,85 @@ const CalendarPermissions = () => { )} - - - - Account Details - - - {userIsFetching && } - {userError && Error loading user} - {!userIsFetching && ( - { - return ( - - - - - {usersError && Failed to load list of users} - - - - {usersError && Failed to load list of users} - - - + {userIsFetching && } + {userError && Error loading user} + {!userIsFetching && ( + { + return ( + + + + + {usersError && Failed to load list of users} + + + + {usersError && Failed to load list of users} + + + + {usersError && Failed to load list of users} + + + + + + Edit Permissions + {postResults.isFetching && ( + - {usersError && Failed to load list of users} - - - - - - Edit Permissions - {postResults.isFetching && ( - - )} - - - - {postResults.isSuccess && ( - {postResults.data?.Results} - )} - - ) - }} - /> - )} - - - - - - - Current Permissions - - - {userIsFetching && } - {!userIsFetching && ( - <> - {user.length > 0 && ( - - )} - - )} - - + )} + + + + {postResults.isSuccess && ( + {postResults.data?.Results} + )} + + ) + }} + /> + )} )} - + ) } @@ -557,7 +539,6 @@ const MailboxForwarding = () => { data: user = {}, isFetching: userIsFetching, error: userError, - refetch: refetchPermissions, } = useListMailboxPermissionsQuery({ tenantDomain, userId }) const { @@ -568,7 +549,6 @@ const MailboxForwarding = () => { useEffect(() => { if (postResults.isSuccess) { - refetchPermissions() } if (!userId || !tenantDomain) { ModalService.open({ @@ -579,7 +559,7 @@ const MailboxForwarding = () => { } else { setQueryError(false) } - }, [userId, tenantDomain, dispatch, postResults, refetchPermissions]) + }, [userId, tenantDomain, dispatch, postResults]) const onSubmit = (values) => { const shippedValues = { userid: userId, @@ -616,7 +596,7 @@ const MailboxForwarding = () => { const formDisabled = queryError === true return ( - + <> {!queryError && ( <> {queryError && ( @@ -630,139 +610,122 @@ const MailboxForwarding = () => { )} - - - - Account Details - {userId} - - - {usersIsFetching && } - {userError && Error loading user} - {!usersIsFetching && ( - { - return ( - - - -
    - -
    - {values.forwardOption === 'internalAddress' && ( - ({ - value: user.mail, - name: `${user.displayName} - ${user.mail} `, - }))} - placeholder={!usersIsFetching ? 'Select user' : 'Loading...'} - name="ForwardInternal" - /> - )} - {usersError && Failed to load list of users} -
    -
    - - -
    - -
    - {values.forwardOption === 'ExternalAddress' && ( - - )} -
    -
    - - -
    - -
    -
    -
    - - - - - Edit Forwarding - {postResults.isFetching && ( - - )} - - - - {postResults.isSuccess && ( - - {postResults.data.Results.map((result, idx) => ( -
  • {result}
  • - ))} -
    + + {usersIsFetching && } + {userError && Error loading user} + {!usersIsFetching && ( + { + return ( + + + +
    + +
    + {values.forwardOption === 'internalAddress' && ( + ({ + value: user.mail, + name: `${user.displayName} - ${user.mail} `, + }))} + placeholder={!usersIsFetching ? 'Select user' : 'Loading...'} + name="ForwardInternal" + /> )} -
    - ) - }} - /> - )} -
    -
    -
    - - - - Account Details - {userId} - - - - - + {usersError && Failed to load list of users} + +
    + + +
    + +
    + {values.forwardOption === 'ExternalAddress' && ( + + )} +
    +
    + + +
    + +
    +
    +
    + + + + + Edit Forwarding + {postResults.isFetching && ( + + )} + + + + {postResults.isSuccess && ( + + {postResults.data.Results.map((result, idx) => ( +
  • {result}
  • + ))} +
    + )} + + ) + }} + /> + )} )} -
    + ) } @@ -784,14 +747,17 @@ const ForwardingSettings = () => { return ( - - {content.map((item, index) => ( -
    -
    {item.heading}
    -

    {item.body}

    -
    - ))} -
    + {isFetching && } + {!isFetching && ( + + {content.map((item, index) => ( +
    +
    {item.heading}
    +

    {item.body}

    +
    + ))} +
    + )}
    ) } From 8849b22cc83a8253a5b120dc1f6613ff8682a2b3 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 21 Sep 2023 17:23:19 -0400 Subject: [PATCH 09/25] Webhooks - Add Service Principal operations --- src/views/tenant/administration/AlertWizard.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/views/tenant/administration/AlertWizard.js b/src/views/tenant/administration/AlertWizard.js index b0d8a9dbdfbf..28bf204948d5 100644 --- a/src/views/tenant/administration/AlertWizard.js +++ b/src/views/tenant/administration/AlertWizard.js @@ -222,6 +222,14 @@ const AlertWizard = () => { value: 'UserLoggedIn', name: 'A user has logged in from any location', }, + { + value: 'Add service principal.', + name: 'Enterprise App Added', + }, + { + value: 'Remove service principal.', + name: 'Enterprise App Removed', + }, ]} /> From 8032a4b42f2144cd6033d0e0e2c53af0addc52a8 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 22 Sep 2023 13:35:25 +0200 Subject: [PATCH 10/25] added "see more" to home --- src/views/home/Home.js | 62 ++++++++++++++++++++++++++---------------- 1 file changed, 39 insertions(+), 23 deletions(-) diff --git a/src/views/home/Home.js b/src/views/home/Home.js index e3025e587fda..5429c137e258 100644 --- a/src/views/home/Home.js +++ b/src/views/home/Home.js @@ -1,4 +1,4 @@ -import React from 'react' +import React, { useState } from 'react' import { faBook, faCog, @@ -16,7 +16,7 @@ import { faUsers, faServer, } from '@fortawesome/free-solid-svg-icons' -import { CCol, CRow } from '@coreui/react' +import { CButton, CCol, CCollapse, CRow } from '@coreui/react' import { useGenericGetRequestQuery } from 'src/store/api/app' import { CippContentCard } from 'src/components/layout' import Skeleton from 'react-loading-skeleton' @@ -30,6 +30,8 @@ import Portals from 'src/data/portals' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' const Home = () => { + const [visible, setVisible] = useState(false) + const currentTenant = useSelector((state) => state.app.currentTenant) const { data: organization, @@ -62,7 +64,7 @@ const Home = () => { }) const { - data: standards, + data: standards = [], isLoading: isLoadingStandards, isSuccess: issuccessStandards, isFetching: isFetchingStandards, @@ -124,6 +126,21 @@ const Home = () => { icon: faUserFriends, }, ] + + const filteredStandards = standards + .filter( + (p) => p.displayName === 'AllTenants' || p.displayName === currentTenant.defaultDomainName, + ) + .flatMap((tenant) => { + return Object.keys(tenant.standards).map((standard) => { + const standardDisplayname = allStandardsList.filter((p) => p.name.includes(standard)) + return ( +
  • + {standardDisplayname[0]?.label} ({tenant.displayName}) +
  • + ) + }) + }) return ( <> @@ -261,26 +278,25 @@ const Home = () => {

    Applied Standards

    {(isLoadingStandards || isFetchingStandards) && } - {issuccessStandards && - !isFetchingStandards && - standards - .filter( - (p) => - p.displayName == 'AllTenants' || - p.displayName == currentTenant.defaultDomainName, - ) - .flatMap((tenant) => { - return Object.keys(tenant.standards).map((standard) => { - const standardDisplayname = allStandardsList.filter((p) => - p.name.includes(standard), - ) - return ( -
  • - {standardDisplayname[0]?.label} ({tenant.displayName}) -
  • - ) - }) - })} + + {issuccessStandards && !isFetchingStandards && ( + <> + {filteredStandards.slice(0, 5)} + + {filteredStandards.length > 5 && ( + <> + {filteredStandards.slice(5)} + setVisible(!visible)} + > + {visible ? 'See less' : 'See more...'} + + + )} + + )}

    Partner Relationships

    From b4e18a8cba0ae559980b07f3e1b761d8d398e6a0 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Sat, 23 Sep 2023 23:12:44 +0200 Subject: [PATCH 11/25] Fixed refresh issue --- src/views/cipp/CIPPSettings.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/views/cipp/CIPPSettings.js b/src/views/cipp/CIPPSettings.js index fe54e4805cc2..00deb2d85098 100644 --- a/src/views/cipp/CIPPSettings.js +++ b/src/views/cipp/CIPPSettings.js @@ -1069,6 +1069,7 @@ const NotificationsSettings = () => { true} initialValues={{ ...notificationListResult.data, logsToInclude: notificationListResult.data?.logsToInclude?.map((m) => ({ From 53100dbaf5d3b28cee798120ab700e548cbfe621 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Sat, 23 Sep 2023 23:12:57 +0200 Subject: [PATCH 12/25] add unix time handle --- src/components/tables/CellDate.js | 51 ++++++++++++++----------------- 1 file changed, 23 insertions(+), 28 deletions(-) diff --git a/src/components/tables/CellDate.js b/src/components/tables/CellDate.js index 9806f137c290..04f7bf9c5f3d 100644 --- a/src/components/tables/CellDate.js +++ b/src/components/tables/CellDate.js @@ -4,13 +4,6 @@ import PropTypes from 'prop-types' import { CTooltip } from '@coreui/react' import ReactTimeAgo from 'react-time-ago' -/** - * - * @param format ['short', 'long', 'relative'] - * @param value - * @returns {JSX.Element} - * @constructor - */ export const CellDate = ({ format = 'short', showTime = true, showDate = true, cell }) => { if (!cell || (!showTime && !showDate)) { return
    @@ -21,25 +14,29 @@ export const CellDate = ({ format = 'short', showTime = true, showDate = true, c locale = navigator.language } - // cheatsheet - // https://devhints.io/wip/intl-datetime - const dateTimeArgs = [ - [locale, 'default'], // add fallback option if locale doesn't load properly - ] + // Convert cell value to a number and check if it's a Unix timestamp + const possibleUnixTimestamp = Number(cell) + const isUnixTimestamp = !isNaN(possibleUnixTimestamp) && possibleUnixTimestamp > 1000000000 + let dateObject + + if (isUnixTimestamp) { + dateObject = moment.unix(possibleUnixTimestamp).toDate() + } else { + dateObject = moment(cell).toDate() + } + + const dateTimeArgs = [[locale, 'default']] const dateTimeFormatOptions = {} - if (format == 'relative') { + + if (format === 'relative') { try { - return ( - - - - ) + return } catch (error) { - console.error('Error formatting date, fallback to string value', { date: cell, error }) + console.error('Error formatting date, fallback to string value', { date: dateObject, error }) return ( - -
    {String(cell)}
    + +
    {String(dateObject)}
    ) } @@ -56,16 +53,14 @@ export const CellDate = ({ format = 'short', showTime = true, showDate = true, c let formatted try { - // lots of dates returned are unreliably parsable (e.g. non ISO8601 format) - // fallback using moment to parse into date object - formatted = new Intl.DateTimeFormat(...dateTimeArgs).format(moment(cell).toDate()) + formatted = new Intl.DateTimeFormat(...dateTimeArgs).format(dateObject) } catch (error) { - console.error('Error formatting date, fallback to string value', { date: cell, error }) - formatted = cell + console.error('Error formatting date, fallback to string value', { date: dateObject, error }) + formatted = dateObject.toString() } return ( - +
    {String(formatted)}
    ) @@ -74,7 +69,7 @@ export const CellDate = ({ format = 'short', showTime = true, showDate = true, c CellDate.propTypes = { format: PropTypes.oneOf(['short', 'medium', 'long', 'full', 'relative']), - cell: PropTypes.oneOfType([PropTypes.string, PropTypes.instanceOf(Date)]), + cell: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.instanceOf(Date)]), showTime: PropTypes.bool, showDate: PropTypes.bool, } From 4fcc8380e30f8e7b90af4927763ca4d169757284 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Sat, 23 Sep 2023 23:13:12 +0200 Subject: [PATCH 13/25] add text cases for badges --- src/components/tables/CellBadge.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/components/tables/CellBadge.js b/src/components/tables/CellBadge.js index 5234f65e2fca..26759c874f60 100644 --- a/src/components/tables/CellBadge.js +++ b/src/components/tables/CellBadge.js @@ -3,6 +3,24 @@ import React from 'react' import { CBadge } from '@coreui/react' export const CellBadge = ({ label = '', color = '', children, ...rest }) => { + //Create a case select, and return the color based on the label + switch (label.toLowerCase()) { + case 'planned': + color = 'info' + break + case 'failed': + color = 'danger' + break + case 'completed': + color = 'success' + break + case 'Banned': + color = 'danger' + break + default: + color = 'primary' + } + return ( {label} From 067057dbb9d4b1251097eea4c3e9e8acb1a96434 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Sat, 23 Sep 2023 23:14:20 +0200 Subject: [PATCH 14/25] fix stuff --- src/components/tables/CellBadge.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/tables/CellBadge.js b/src/components/tables/CellBadge.js index 26759c874f60..ae6b948ce83f 100644 --- a/src/components/tables/CellBadge.js +++ b/src/components/tables/CellBadge.js @@ -14,10 +14,10 @@ export const CellBadge = ({ label = '', color = '', children, ...rest }) => { case 'completed': color = 'success' break - case 'Banned': + case 'banned': color = 'danger' break - default: + case 'running': color = 'primary' } From 6ef5addd9713554e8424804cba41ef42f94e5193 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Sat, 23 Sep 2023 23:16:43 +0200 Subject: [PATCH 15/25] added scheduler --- src/_nav.js | 5 + src/adminRoutes.js | 3 + src/components/utilities/CippCodeOffcanvas.js | 37 +- src/views/cipp/Scheduler.js | 337 ++++++++++++++++++ 4 files changed, 368 insertions(+), 14 deletions(-) create mode 100644 src/views/cipp/Scheduler.js diff --git a/src/_nav.js b/src/_nav.js index 2f8d05e25f85..c243b18c4e12 100644 --- a/src/_nav.js +++ b/src/_nav.js @@ -663,6 +663,11 @@ const _nav = [ name: 'Settings', to: '/cipp/settings', }, + { + component: CNavItem, + name: 'Scheduler', + to: '/cipp/scheduler', + }, { component: CNavItem, name: 'SAM Setup Wizard', diff --git a/src/adminRoutes.js b/src/adminRoutes.js index b57360064384..8b10173655ab 100644 --- a/src/adminRoutes.js +++ b/src/adminRoutes.js @@ -11,12 +11,15 @@ const GDAPRelationships = React.lazy(() => import('./views/tenant/administration/ListGDAPRelationships'), ) const appapproval = React.lazy(() => import('src/views/cipp/AppApproval')) +const Scheduler = React.lazy(() => import('src/views/cipp/Scheduler')) const adminRoutes = [ { path: '/cipp', name: 'CIPP' }, { path: '/cipp/cipp', name: 'CIPP' }, { path: '/cipp/settings', name: 'Settings', component: CIPPSettings }, { path: '/cipp/setup', name: 'Setup', component: Setup }, + { path: '/cipp/scheduler', name: 'Scheduler', component: Scheduler }, + { path: '/tenant/administration/gdap', name: 'GDAP Wizard', component: GDAP }, { path: '/tenant/administration/gdap-invite', name: 'GDAP Invite Wizard', component: GDAPInvite }, { diff --git a/src/components/utilities/CippCodeOffcanvas.js b/src/components/utilities/CippCodeOffcanvas.js index c8cdbf175d35..38a23b81663f 100644 --- a/src/components/utilities/CippCodeOffcanvas.js +++ b/src/components/utilities/CippCodeOffcanvas.js @@ -6,7 +6,14 @@ import { useLazyGenericGetRequestQuery, useLazyGenericPostRequestQuery } from 's import { Editor } from '@monaco-editor/react' import { useSelector } from 'react-redux' -function CippCodeOffCanvas({ row, state, hideFunction, type }) { +function CippCodeOffCanvas({ + row, + state, + hideFunction, + type, + title = 'Template JSON', + hideButton = false, +}) { const [SaveTemplate, templateDetails] = useLazyGenericPostRequestQuery() const currentTheme = useSelector((state) => state.app.currentTheme) const [templateData, setFormData] = useState(row) @@ -23,7 +30,7 @@ function CippCodeOffCanvas({ row, state, hideFunction, type }) { return ( <> - - SaveTemplate({ - path: `/api/ExecEditTemplate?type=${type}`, - method: 'POST', - values: templateData, - }) - } - > - Save changes {templateDetails.isFetching && } - + {!hideButton && ( + + SaveTemplate({ + path: `/api/ExecEditTemplate?type=${type}`, + method: 'POST', + values: templateData, + }) + } + > + Save changes {templateDetails.isFetching && } + + )} {templateDetails.isSuccess && !templateDetails.isFetching && ( diff --git a/src/views/cipp/Scheduler.js b/src/views/cipp/Scheduler.js new file mode 100644 index 000000000000..e0af97bcb8c8 --- /dev/null +++ b/src/views/cipp/Scheduler.js @@ -0,0 +1,337 @@ +import React, { useEffect, useState } from 'react' +import { CButton, CCallout, CCol, CForm, CFormLabel, CRow, CSpinner, CTooltip } from '@coreui/react' +import useQuery from 'src/hooks/useQuery' +import { useDispatch, useSelector } from 'react-redux' +import { Field, Form } from 'react-final-form' +import { + Condition, + RFFCFormCheck, + RFFCFormInput, + RFFCFormSwitch, + RFFCFormTextarea, + RFFSelectSearch, +} from 'src/components/forms' +import countryList from 'src/data/countryList' + +import { useLazyGenericGetRequestQuery, useLazyGenericPostRequestQuery } from 'src/store/api/app' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { faCircleNotch, faEdit, faEye } from '@fortawesome/free-solid-svg-icons' +import { CippContentCard, CippPage, CippPageList } from 'src/components/layout' +import { password } from 'src/validators' +import { + CellDate, + CellDelegatedPrivilege, + cellBadgeFormatter, + cellBooleanFormatter, + cellDateFormatter, +} from 'src/components/tables' +import { CellTip, cellGenericFormatter } from 'src/components/tables/CellGenericFormat' +import DatePicker from 'react-datepicker' +import 'react-datepicker/dist/react-datepicker.css' +import TenantListSelector from 'src/components/utilities/TenantListSelector' +import { ModalService, TenantSelector } from 'src/components/utilities' +import CippCodeOffCanvas from 'src/components/utilities/CippCodeOffcanvas' + +const Offcanvas = (row, rowIndex, formatExtraData) => { + const [ExecuteGetRequest, getResults] = useLazyGenericGetRequestQuery() + const [ocVisible, setOCVisible] = useState(false) + + const handleDeleteSchedule = (apiurl, message) => { + ModalService.confirm({ + title: 'Confirm', + body:
    {message}
    , + onConfirm: () => ExecuteGetRequest({ path: apiurl }), + confirmLabel: 'Continue', + cancelLabel: 'Cancel', + }) + } + let jsonResults + try { + jsonResults = JSON.parse(row.Results) + } catch (error) { + jsonResults = row.Results + } + + return ( + <> + + setOCVisible(true)}> + + + + + + handleDeleteSchedule( + `/api/RemoveScheduledItem?&ID=${row.RowKey}`, + 'Do you want to delete this job?', + ) + } + size="sm" + variant="ghost" + color="danger" + > + + + + setOCVisible(false)} + /> + + ) +} + +const Scheduler = () => { + const currentDate = new Date() + const [startDate, setStartDate] = useState(currentDate) + const tenantDomain = useSelector((state) => state.app.currentTenant.defaultDomainName) + const [refreshState, setRefreshState] = useState(false) + const taskName = `Scheduled Task ${currentDate.toLocaleString()}` + + const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery() + const onSubmit = (values) => { + const unixTime = Math.floor(startDate.getTime() / 1000) + const shippedValues = { + TenantFilter: tenantDomain, + Name: values.taskName, + Command: values.command, + Parameters: values.parameters, + ScheduledTime: unixTime, + Recurrence: values.Recurrence, + PostExecution: { + Webhook: values.webhook, + Email: values.email, + PSA: values.psa, + }, + } + genericPostRequest({ path: '/api/AddScheduledItem', values: shippedValues }).then((res) => { + setRefreshState(res.requestId) + console.log(res.requestId) + }) + } + + const columns = [ + { + name: 'Name', + selector: (row) => row['Name'], + sortable: true, + cell: (row) => CellTip(row['Name']), + exportSelector: 'Name', + }, + { + name: 'Tenant', + selector: (row) => row['Tenant'], + sortable: true, + cell: (row) => CellTip(row['Tenant']), + exportSelector: 'Tenant', + }, + { + name: 'Task State', + selector: (row) => row['TaskState'], + sortable: true, + cell: cellBadgeFormatter(), + exportSelector: 'TaskState', + }, + { + name: 'Command', + selector: (row) => row['Command'], + sortable: true, + cell: (row) => CellTip(row['Command']), + exportSelector: 'Command', + }, + { + name: 'Parameters', + selector: (row) => row['Parameters'], + sortable: true, + cell: (row) => CellTip(row['Parameters']), + exportSelector: 'Parameters', + }, + { + name: 'Scheduled Time', + selector: (row) => row['ScheduledTime'], + sortable: true, + cell: cellDateFormatter({ format: 'relative' }), + exportSelector: 'ScheduledTime', + }, + { + name: 'Last executed time', + selector: (row) => row['ExecutedTime'], + sortable: true, + cell: cellDateFormatter({ format: 'relative' }), + exportSelector: 'ExecutedTime', + }, + { + name: 'Recurrence', + selector: (row) => row['Recurrence'], + sortable: true, + cell: (row) => CellTip(row['Recurrence']), + exportSelector: 'Recurrence', + }, + { + name: 'Sending to', + selector: (row) => row['PostExecution'], + sortable: true, + cell: (row) => CellTip(row['PostExecution']), + exportSelector: 'PostExecution', + }, + { + name: 'Actions', + cell: Offcanvas, + maxWidth: '80px', + }, + ] + return ( + + <> + + + + true} + render={({ handleSubmit, submitting, values }) => { + return ( + + + + + {(props) => } + + + + + + + + + + + setStartDate(date)} + /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Add Schedule + {postResults.isFetching && ( + + )} + + + + {postResults.isSuccess && ( + +
  • {postResults.data.Results}
  • +
    + )} +
    + ) + }} + /> +
    +
    + + + +
    + +
    + ) +} + +export default Scheduler From 5ea3937738e255c9872c5758893be18630efb57d Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Sun, 24 Sep 2023 02:50:26 +0200 Subject: [PATCH 16/25] added allow initial value for checkboxes so we always send what we need. --- src/components/forms/RFFComponents.js | 3 +- src/views/cipp/Scheduler.js | 73 ++++++++++++++++++++++----- 2 files changed, 61 insertions(+), 15 deletions(-) diff --git a/src/components/forms/RFFComponents.js b/src/components/forms/RFFComponents.js index a360ec2fc49e..9aef7ca81c35 100644 --- a/src/components/forms/RFFComponents.js +++ b/src/components/forms/RFFComponents.js @@ -88,9 +88,10 @@ export const RFFCFormSwitch = ({ className = 'mb-3', validate, disabled = false, + initialValue, }) => { return ( - + {({ meta, input }) => ( { const tenantDomain = useSelector((state) => state.app.currentTenant.defaultDomainName) const [refreshState, setRefreshState] = useState(false) const taskName = `Scheduled Task ${currentDate.toLocaleString()}` - + const { data: availableCommands = [], isLoading: isLoadingcmd } = useGenericGetRequestQuery({ + path: 'api/ListFunctionParameters?Module=CIPPCore', + }) const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery() const onSubmit = (values) => { const unixTime = Math.floor(startDate.getTime() / 1000) @@ -111,7 +118,6 @@ const Scheduler = () => { } genericPostRequest({ path: '/api/AddScheduledItem', values: shippedValues }).then((res) => { setRefreshState(res.requestId) - console.log(res.requestId) }) } @@ -247,22 +253,61 @@ const Scheduler = () => { ({ + value: cmd.Function, + name: cmd.Function, + }))} name="command" - placeholder="Select a command or report to execute." + placeholder={ + isLoadingcmd ? ( + + ) : ( + 'Select a command or report to execute.' + ) + } label="Command to execute" /> - - - + + {(props) => { + const selectedCommand = availableCommands.find( + (cmd) => cmd.Function === props.values.command?.value, + ) + let paramblock = null + if (selectedCommand) { + //if the command parameter type is boolean we use else . + const parameters = selectedCommand.Parameters + if (parameters.length > 0) { + paramblock = parameters.map((param, idx) => ( + + + {param.Type === 'System.Boolean' ? ( + <> + + + + ) : ( + + )} + + + )) + } + } + return paramblock + }} + From d9be03c6642a082013f92496325cdc99b889ede7 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Sun, 24 Sep 2023 17:53:04 +0200 Subject: [PATCH 17/25] add advanced filtering. --- src/components/tables/CippTable.js | 59 ++++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 3 deletions(-) diff --git a/src/components/tables/CippTable.js b/src/components/tables/CippTable.js index fa11d257ea78..949bfb167989 100644 --- a/src/components/tables/CippTable.js +++ b/src/components/tables/CippTable.js @@ -199,11 +199,64 @@ export default function CippTable({ } } const [resetPaginationToggle, setResetPaginationToggle] = React.useState(false) - const filteredItems = Array.isArray(data) - ? data.filter( + // Helper function to escape special characters in a string for regex + function escapeRegExp(string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + } + + const filterData = (data, filterText) => { + if (filterText.startsWith('Complex:')) { + const conditions = filterText.slice(9).split(';') + + return conditions.reduce((filteredData, condition) => { + const match = condition.trim().match(/(\w+)\s*(eq|ne|like|notlike|gt|lt)\s*(.+)/) + + if (!match) { + return filteredData // Keep the current filtered data as is + } + + let [property, operator, value] = match.slice(1) + value = escapeRegExp(value) // Escape special characters + + return filteredData.filter((item) => { + // Find the actual key in the item that matches the property (case insensitive) + const actualKey = Object.keys(item).find( + (key) => key.toLowerCase() === property.toLowerCase(), + ) + + if (!actualKey) { + //set the error message so the user understands the key is not found. + console.error(`FilterError: Property "${property}" not found.`) + return false // Keep the item if the property is not found + } else { + } + + switch (operator) { + case 'eq': + return String(item[actualKey]).toLowerCase() === value.toLowerCase() + case 'ne': + return String(item[actualKey]).toLowerCase() !== value.toLowerCase() + case 'like': + return String(item[actualKey]).toLowerCase().includes(value.toLowerCase()) + case 'notlike': + return !String(item[actualKey]).toLowerCase().includes(value.toLowerCase()) + case 'gt': + return parseFloat(item[actualKey]) > parseFloat(value) + case 'lt': + return parseFloat(item[actualKey]) < parseFloat(value) + default: + return true + } + }) + }, data) + } else { + return data.filter( (item) => JSON.stringify(item).toLowerCase().indexOf(filterText.toLowerCase()) !== -1, ) - : [] + } + } + + const filteredItems = Array.isArray(data) ? filterData(data, filterText) : [] const applyFilter = (e) => { setFilterText(e.target.value) From 56bdd1580c014009f3f513976122163f922dcda4 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Sun, 24 Sep 2023 22:20:57 +0200 Subject: [PATCH 18/25] added exclude onmicrosoft --- src/views/tenant/standards/DomainsAnalyser.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/views/tenant/standards/DomainsAnalyser.js b/src/views/tenant/standards/DomainsAnalyser.js index 6b8bd505d756..a92501851bec 100644 --- a/src/views/tenant/standards/DomainsAnalyser.js +++ b/src/views/tenant/standards/DomainsAnalyser.js @@ -274,6 +274,12 @@ const DomainsAnalyser = () => { tenantSelector={true} showAllTenantSelector={true} datatable={{ + filterlist: [ + { + filterName: 'Exclude onmicrosoft domains', + filter: 'Complex: domain notlike onmicrosoft', + }, + ], path: `/api/DomainAnalyser_List`, params: { tenantFilter: currentTenant.defaultDomainName }, columns, From 303c3b77a493a1244e3d159682647857b63d6cef Mon Sep 17 00:00:00 2001 From: John Duprey Date: Sun, 24 Sep 2023 18:25:16 -0400 Subject: [PATCH 19/25] Scheduler: Update text and filters --- src/views/cipp/Scheduler.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/views/cipp/Scheduler.js b/src/views/cipp/Scheduler.js index 6b209b06d58d..8483c2c477b2 100644 --- a/src/views/cipp/Scheduler.js +++ b/src/views/cipp/Scheduler.js @@ -358,17 +358,19 @@ const Scheduler = () => { label: 'Delete task', modal: true, modalUrl: `/api/RemoveScheduledItem?&ID=!RowKey`, - modalMessage: 'Are you sure you want to exclude these tenants?', + modalMessage: 'Do you want to delete this job?', }, ], }, filterlist: [ - { filterName: 'Excluded Tenants', filter: '"Excluded":true' }, - { filterName: 'Included Tenants', filter: '"Excluded":false' }, + { filterName: 'Planned Jobs', filter: 'Complex: TaskState eq Planned' }, + { filterName: 'Completed Jobs', filter: 'Complex: TaskState eq Completed' }, + { filterName: 'Recurring Jobs', filter: 'Complex: Recurrence gt 0' }, + { filterName: 'One-time Jobs', filter: 'Complex: Recurrence eq 0' }, ], keyField: 'id', columns, - reportName: `Tenants-List`, + reportName: `Scheduled-Jobs`, path: `/api/ListScheduledItems?RefreshGuid=${refreshState}`, }} /> From 6924f35956162140f8d19c80187958052ccbba13 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Sun, 24 Sep 2023 22:25:37 -0400 Subject: [PATCH 20/25] CippTable - Graph filter rewrite - Remove graphFilter property from filter lists - Add debounce handler for delayed input changes - Update preset graph filters to new format - Move errors to CCallout and continue to render table --- src/components/tables/CippTable.js | 173 ++++++++---------- src/views/identity/administration/Users.js | 6 +- .../administration/ListEnterpriseApps.js | 8 +- .../administration/ListGDAPRelationships.js | 2 +- 4 files changed, 84 insertions(+), 105 deletions(-) diff --git a/src/components/tables/CippTable.js b/src/components/tables/CippTable.js index 949bfb167989..3bc66a7a0547 100644 --- a/src/components/tables/CippTable.js +++ b/src/components/tables/CippTable.js @@ -1,4 +1,4 @@ -import React, { useRef } from 'react' +import React, { useRef, useMemo, useState } from 'react' import { useSelector } from 'react-redux' import { ExportCsvButton, ExportPDFButton } from 'src/components/buttons' import { @@ -25,16 +25,9 @@ import { cellGenericFormatter } from './CellGenericFormat' import { ModalService } from '../utilities' import { useLazyGenericGetRequestQuery, useLazyGenericPostRequestQuery } from 'src/store/api/app' import { ConfirmModal } from '../utilities/SharedModal' -import { useState } from 'react' +import { debounce } from 'lodash' -const FilterComponent = ({ - filterText, - onFilter, - onClear, - filterlist, - onFilterPreset, - onFilterGraph, -}) => ( +const FilterComponent = ({ filterText, onFilter, onClear, filterlist, onFilterPreset }) => ( <> @@ -50,26 +43,17 @@ const FilterComponent = ({ { onFilterPreset('') - onFilterGraph('') }} > Clear Filter {filterlist && filterlist.map((item, idx) => { - if (item.hasOwnProperty('graphFilter') && item.graphFilter == true) { - return ( - onFilterGraph(item.filter)}> - {item.filterName} - - ) - } else { - return ( - onFilterPreset(item.filter)}> - {item.filterName} - - ) - } + return ( + onFilterPreset(item.filter)}> + {item.filterName} + + ) })} @@ -93,7 +77,6 @@ FilterComponent.propTypes = { onClear: PropTypes.func, filterlist: PropTypes.arrayOf(PropTypes.object), onFilterPreset: PropTypes.func, - onFilterGraph: PropTypes.func, } const customSort = (rows, selector, direction) => { @@ -204,8 +187,23 @@ export default function CippTable({ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') } + const setGraphFilter = (e) => { + if (graphFilterFunction) { + graphFilterFunction(e) + console.log(e) + } + } + + const debounceSetGraphFilter = useMemo(() => { + return debounce(setGraphFilter, 1000) + }, []) + const filterData = (data, filterText) => { - if (filterText.startsWith('Complex:')) { + if (filterText.startsWith('Graph:')) { + var query = filterText.slice(6).trim() + debounceSetGraphFilter(query) + return data + } else if (filterText.startsWith('Complex:')) { const conditions = filterText.slice(9).split(';') return conditions.reduce((filteredData, condition) => { @@ -262,12 +260,6 @@ export default function CippTable({ setFilterText(e.target.value) } - const setGraphFilter = (e) => { - if (graphFilterFunction) { - graphFilterFunction(e) - } - } - useEffect(() => { if (columns !== updatedColumns) { setUpdatedColumns(columns) @@ -592,11 +584,6 @@ export default function CippTable({ onFilter={(e) => setFilterText(e.target.value)} onFilterPreset={(e) => { setFilterText(e) - setGraphFilter('') - }} - onFilterGraph={(e) => { - setFilterText('') - setGraphFilter(e) }} onClear={handleClear} filterText={filterText} @@ -620,65 +607,61 @@ export default function CippTable({ const tablePageSize = useSelector((state) => state.app.tablePageSize) return (
    - {!isFetching && error && Error loading data} - {!error && ( -
    - {(columns.length === updatedColumns.length || !dynamicColumns) && ( - <> - {(massResults.length >= 1 || loopRunning) && ( - - {massResults.map((message, idx) => { - const results = message.data?.Results - const displayResults = Array.isArray(results) ? results.join(', ') : results + {!isFetching && error && Error loading data} +
    + {(columns.length === updatedColumns.length || !dynamicColumns) && ( + <> + {(massResults.length >= 1 || loopRunning) && ( + + {massResults.map((message, idx) => { + const results = message.data?.Results + const displayResults = Array.isArray(results) ? results.join(', ') : results - return
  • {displayResults}
  • - })} - {loopRunning && ( -
  • - -
  • - )} -
    - )} - } - paginationRowsPerPageOptions={[25, 50, 100, 200, 500]} - {...rest} - /> - {selectedRows.length >= 1 && ( - Selected {selectedRows.length} items - )} - - )} -
    - )} + return
  • {displayResults}
  • + })} + {loopRunning && ( +
  • + +
  • + )} +
    + )} + } + paginationRowsPerPageOptions={[25, 50, 100, 200, 500]} + {...rest} + /> + {selectedRows.length >= 1 && Selected {selectedRows.length} items} + + )} +
    ) } diff --git a/src/views/identity/administration/Users.js b/src/views/identity/administration/Users.js index 928853445e6a..bd3df44d1aa4 100644 --- a/src/views/identity/administration/Users.js +++ b/src/views/identity/administration/Users.js @@ -356,13 +356,11 @@ const Users = (row) => { { filterName: 'Users without a license', filter: '"assignedLicenses":[]' }, { filterName: 'Users with a license (Graph)', - filter: 'assignedLicenses/$count ne 0', - graphFilter: true, + filter: 'Graph: assignedLicenses/$count ne 0', }, { filterName: 'Users with a license & Enabled (Graph)', - filter: 'assignedLicenses/$count ne 0 and accountEnabled eq true', - graphFilter: true, + filter: 'Graph: assignedLicenses/$count ne 0 and accountEnabled eq true', }, ], columns, diff --git a/src/views/tenant/administration/ListEnterpriseApps.js b/src/views/tenant/administration/ListEnterpriseApps.js index 8e1a3bfa9b01..d6ae08d59059 100644 --- a/src/views/tenant/administration/ListEnterpriseApps.js +++ b/src/views/tenant/administration/ListEnterpriseApps.js @@ -75,7 +75,6 @@ const EnterpriseApplications = () => { selector: (row) => row.homepage, sortable: true, exportSelector: 'homepage', - cell: cellDateFormatter({ format: 'short' }), }, ] return ( @@ -88,13 +87,12 @@ const EnterpriseApplications = () => { filterlist: [ { filterName: 'All Enterprise Apps', - filter: "tags/any(t:t eq 'WindowsAzureActiveDirectoryIntegratedApp')", - graphFilter: true, + filter: "Graph: tags/any(t:t eq 'WindowsAzureActiveDirectoryIntegratedApp')", }, { filterName: 'Enterprise Apps (SAML)', - filter: "tags/any(t:t eq 'WindowsAzureActiveDirectoryGalleryApplicationPrimaryV1')", - graphFilter: true, + filter: + "Graph: tags/any(t:t eq 'WindowsAzureActiveDirectoryGalleryApplicationPrimaryV1')", }, ], tableProps: { diff --git a/src/views/tenant/administration/ListGDAPRelationships.js b/src/views/tenant/administration/ListGDAPRelationships.js index 8efe4f6795ee..a5703bd84b22 100644 --- a/src/views/tenant/administration/ListGDAPRelationships.js +++ b/src/views/tenant/administration/ListGDAPRelationships.js @@ -68,7 +68,7 @@ const Actions = (row, rowIndex, formatExtraData) => { }) const tenant = useSelector((state) => state.app.currentTenant) - row?.accessDetails.unifiedRoles.map((role) => { + row?.accessDetails?.unifiedRoles?.map((role) => { for (var x = 0; x < GDAPRoles.length; x++) { if (GDAPRoles[x].ObjectId == role.roleDefinitionId) { extendedInfo.push({ From 8fd42f373d638c909f703fdd1ad3ec77aa7900b9 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Mon, 25 Sep 2023 12:31:19 +0200 Subject: [PATCH 21/25] text fix --- src/views/email-exchange/spamfilter/DeploySpamfilter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/email-exchange/spamfilter/DeploySpamfilter.js b/src/views/email-exchange/spamfilter/DeploySpamfilter.js index 8c3a470efb20..f798ebf48003 100644 --- a/src/views/email-exchange/spamfilter/DeploySpamfilter.js +++ b/src/views/email-exchange/spamfilter/DeploySpamfilter.js @@ -140,7 +140,7 @@ const SpamFilterAdd = () => { Date: Mon, 25 Sep 2023 13:34:19 +0200 Subject: [PATCH 22/25] added TableFilter option via url --- src/components/tables/CippTable.js | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/components/tables/CippTable.js b/src/components/tables/CippTable.js index 3bc66a7a0547..fc09031e33be 100644 --- a/src/components/tables/CippTable.js +++ b/src/components/tables/CippTable.js @@ -1,4 +1,4 @@ -import React, { useRef, useMemo, useState } from 'react' +import React, { useRef, useMemo, useState, useCallback } from 'react' import { useSelector } from 'react-redux' import { ExportCsvButton, ExportPDFButton } from 'src/components/buttons' import { @@ -26,6 +26,7 @@ import { ModalService } from '../utilities' import { useLazyGenericGetRequestQuery, useLazyGenericPostRequestQuery } from 'src/store/api/app' import { ConfirmModal } from '../utilities/SharedModal' import { debounce } from 'lodash' +import { useSearchParams } from 'react-router-dom' const FilterComponent = ({ filterText, onFilter, onClear, filterlist, onFilterPreset }) => ( <> @@ -137,12 +138,20 @@ export default function CippTable({ const [loopRunning, setLoopRunning] = React.useState(false) const [massResults, setMassResults] = React.useState([]) const [filterText, setFilterText] = React.useState('') + const [filterviaURL, setFilterviaURL] = React.useState(false) const [updatedColumns, setUpdatedColumns] = React.useState(columns) const [selectedRows, setSelectedRows] = React.useState(false) const [genericGetRequest, getResults] = useLazyGenericGetRequestQuery() const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery() const [getDrowndownInfo, dropDownInfo] = useLazyGenericGetRequestQuery() const [modalContent, setModalContent] = useState(null) + //get the search params called "tableFilter" and set the filter to that. + const [searchParams, setSearchParams] = useSearchParams() + if (searchParams.get('tableFilter') && !filterviaURL) { + setFilterText(searchParams.get('tableFilter')) + setFilterviaURL(true) + } + useEffect(() => { if (dropDownInfo.isFetching) { handleModal( @@ -198,7 +207,20 @@ export default function CippTable({ return debounce(setGraphFilter, 1000) }, []) + const debounceSetSearchParams = useCallback(() => { + const currentUrl = new URL(window.location.href) + if (filterText !== '') { + currentUrl.searchParams.set('tableFilter', filterText) + window.history.replaceState({}, '', currentUrl.toString()) + } else { + currentUrl.searchParams.delete('tableFilter') + window.history.replaceState({}, '', currentUrl.toString()) + } + }, [filterText]) + const filterData = (data, filterText) => { + const debouncedSetSearchParams = debounce(debounceSetSearchParams, 1000) + debouncedSetSearchParams() if (filterText.startsWith('Graph:')) { var query = filterText.slice(6).trim() debounceSetGraphFilter(query) From 48b9ab63f1fe76a26b17ebf5df4939e98c1915bb Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Mon, 25 Sep 2023 13:41:22 +0200 Subject: [PATCH 23/25] added tableFilter Urls --- src/views/home/Home.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/views/home/Home.js b/src/views/home/Home.js index d1334e1372c9..04fdd9d5cefa 100644 --- a/src/views/home/Home.js +++ b/src/views/home/Home.js @@ -175,7 +175,7 @@ const Home = () => { href={ '/identity/administration/users?customerId=' + currentTenant.customerId + - '&filterName=Users with a license' + '&tableFilter=Graph%3A+assignedLicenses%2F%24count+ne+0' } class="stretched-link" > @@ -206,7 +206,7 @@ const Home = () => { href={ '/identity/administration/users?customerId=' + currentTenant.customerId + - '&filterName=Guest users' + '&tableFilter=Graph%3A+usertype+eq+%27guest%27' } class="stretched-link" > From d7c7c545544389ec19b8f96dfaf8fd840b260a90 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Mon, 25 Sep 2023 14:01:00 +0200 Subject: [PATCH 24/25] use links instead of a's for internal routing --- src/views/home/Home.js | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/src/views/home/Home.js b/src/views/home/Home.js index 04fdd9d5cefa..1473119811aa 100644 --- a/src/views/home/Home.js +++ b/src/views/home/Home.js @@ -28,6 +28,7 @@ import ReactTimeAgo from 'react-time-ago' import { CellDelegatedPrivilege } from 'src/components/tables/CellDelegatedPrivilege' import Portals from 'src/data/portals' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { Link } from 'react-router-dom' const Home = () => { const [visible, setVisible] = useState(false) @@ -160,10 +161,10 @@ const Home = () => { - +
    {issuccessUserCounts && !isFetchingUserCount ? dashboard?.Users : }
    @@ -171,14 +172,14 @@ const Home = () => {
    - + className="stretched-link" + />
    {issuccessUserCounts && !isFetchingUserCount ? dashboard?.LicUsers : }
    @@ -186,15 +187,15 @@ const Home = () => {
    - + />
    {issuccessUserCounts && !isFetchingUserCount ? dashboard?.Gas : }
    @@ -202,14 +203,14 @@ const Home = () => {
    - + className="stretched-link" + />
    {issuccessUserCounts && !isFetchingUserCount ? dashboard?.Guests : }
    From e93d8159643d8daaed3fa2a6ff97d9eef5b65ae1 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Mon, 25 Sep 2023 15:50:33 +0200 Subject: [PATCH 25/25] version up --- public/version_latest.txt | 2 +- version_latest.txt | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 version_latest.txt diff --git a/public/version_latest.txt b/public/version_latest.txt index d87edbfc1069..81911389142b 100644 --- a/public/version_latest.txt +++ b/public/version_latest.txt @@ -1 +1 @@ -4.2.1 \ No newline at end of file +4.3.0 \ No newline at end of file diff --git a/version_latest.txt b/version_latest.txt deleted file mode 100644 index d87edbfc1069..000000000000 --- a/version_latest.txt +++ /dev/null @@ -1 +0,0 @@ -4.2.1 \ No newline at end of file