diff --git a/.github/ISSUE_TEMPLATE/feature.yml b/.github/ISSUE_TEMPLATE/feature.yml index af41440ebfc9..de839daf7a30 100644 --- a/.github/ISSUE_TEMPLATE/feature.yml +++ b/.github/ISSUE_TEMPLATE/feature.yml @@ -11,7 +11,7 @@ body: - Please search the existing feature request to see if there has been a similar issue filed. - - If a feature has been filed before, but not followed up by a contributor, you can develop the feature yourself by checking the development documentation [here](https://cipp.app/docs/dev/). + - If a feature has been filed before, but not followed up by a contributor, you can develop the feature yourself by checking the development documentation [here](https://docs.cipp.app/dev-documentation/cipp-dev-guide/setting-up-for-local-development). - Repeat feature requests are allowed if the previous request has been closed for more than 30 days diff --git a/.github/workflows/Close_Stale_Issues_and_PRs.yml b/.github/workflows/Close_Stale_Issues_and_PRs.yml index 7eff0bf2f619..35bd43d9f8f0 100644 --- a/.github/workflows/Close_Stale_Issues_and_PRs.yml +++ b/.github/workflows/Close_Stale_Issues_and_PRs.yml @@ -9,7 +9,7 @@ jobs: steps: - uses: actions/stale@v4 with: - stale-issue-message: 'This issue is stale because it has been open 10 days with no activity. We will close this issue soon. If you want this feature implemented you can contribute it. See: https://cipp.app/GettingStarted/Contributions/ . Please notify the team if you are working on this yourself.' + stale-issue-message: 'This issue is stale because it has been open 10 days with no activity. We will close this issue soon. If you want this feature implemented you can contribute it. See: https://docs.cipp.app/dev-documentation/contributing-to-the-code . Please notify the team if you are working on this yourself.' close-issue-message: 'This issue was closed because it has been stalled for 14 days with no activity.' stale-issue-label: 'no-activity' exempt-issue-labels: 'planned' diff --git a/.github/workflows/Comment_on_Issues.yml b/.github/workflows/Comment_on_Issues.yml index 80574ef5d235..c43a6c184869 100644 --- a/.github/workflows/Comment_on_Issues.yml +++ b/.github/workflows/Comment_on_Issues.yml @@ -35,5 +35,5 @@ jobs: Your current priority is set to "No Priority". No Priority Feature requests automatically get closed in two days if a contributor does not accept the FR. If you are a sponsor you can request an upgrade of priority. To upgrade the priority type "I would like to upgrade the priority". - If you want this feature to be integrated you can always do this yourself by checking out our contributions guide at https://cipp.app/docs/dev/. Contributors to the CIPP project reserve the right to close feature requests at will. + If you want this feature to be integrated you can always do this yourself by checking out our contributions guide at https://docs.cipp.app/dev-documentation/contributing-to-the-code. Contributors to the CIPP project reserve the right to close feature requests at will. If you'd like this feature request to be assigned to you, please comment "I would like to work on this please!". diff --git a/public/version_latest.txt b/public/version_latest.txt index ae153944ee8b..50021202769b 100644 --- a/public/version_latest.txt +++ b/public/version_latest.txt @@ -1 +1 @@ -4.5.0 \ No newline at end of file +4.5.5 \ No newline at end of file diff --git a/src/_nav.js b/src/_nav.js index 852e4334d8cb..272eb82cd2f8 100644 --- a/src/_nav.js +++ b/src/_nav.js @@ -677,6 +677,25 @@ const _nav = [ }, ], }, + { + component: CNavGroup, + name: 'Tools', + section: 'Tools', + to: '/email/tools', + icon: , + items: [ + { + component: CNavItem, + name: 'Mailbox Restore Wizard', + to: '/email/tools/mailbox-restore-wizard', + }, + { + component: CNavItem, + name: 'Mailbox Restores', + to: '/email/tools/mailbox-restores', + }, + ], + }, { component: CNavTitle, name: 'Settings', diff --git a/src/components/forms/RFFComponents.js b/src/components/forms/RFFComponents.js index 5365819e6623..414a472b6c82 100644 --- a/src/components/forms/RFFComponents.js +++ b/src/components/forms/RFFComponents.js @@ -420,7 +420,7 @@ export const RFFSelectSearch = ({ isMulti={multi} /> )} - + {meta.error && meta.touched && {meta.error}} ) }} diff --git a/src/components/tables/CellGenericFormat.js b/src/components/tables/CellGenericFormat.js index e442378a5633..d78f7759dcd4 100644 --- a/src/components/tables/CellGenericFormat.js +++ b/src/components/tables/CellGenericFormat.js @@ -79,6 +79,9 @@ export const cellGenericFormatter = if (cell.toLowerCase() === 'failed') { return {CellTip('Failed to retrieve from API')} } + if (cell.toLowerCase().startsWith('http')) { + return URL + } return CellTip(cell) } if (typeof cell === 'number') { diff --git a/src/components/utilities/CippActionsOffcanvas.js b/src/components/utilities/CippActionsOffcanvas.js index 371276f49375..ce148defa5a4 100644 --- a/src/components/utilities/CippActionsOffcanvas.js +++ b/src/components/utilities/CippActionsOffcanvas.js @@ -85,6 +85,13 @@ export default function CippActionsOffcanvas(props) { title: 'Confirm', onConfirm: () => genericGetRequest({ path: modalUrl }), }) + } else if (modalType === 'codeblock') { + ModalService.open({ + data: modalBody, + componentType: 'codeblock', + title: 'Info', + size: 'lg', + }) } else { ModalService.confirm({ key: modalContent, diff --git a/src/components/utilities/SharedModal.js b/src/components/utilities/SharedModal.js index 4975c0cc17f6..fb8501fc93ca 100644 --- a/src/components/utilities/SharedModal.js +++ b/src/components/utilities/SharedModal.js @@ -2,6 +2,7 @@ import React from 'react' import { CButton, CModal, CModalBody, CModalFooter, CModalHeader, CModalTitle } from '@coreui/react' import PropTypes from 'prop-types' import { CippTable } from 'src/components/tables' +import CippCodeBlock from 'src/components/utilities/CippCodeBlock' /** * @@ -18,6 +19,10 @@ function mapBodyComponent({ componentType, data, componentProps }) { return
{Array.isArray(data) && data.map((el, idx) =>
{el}
)}
case 'text': return String(data) + case 'codeblock': + return ( + + ) default: return String(data) } diff --git a/src/data/standards.json b/src/data/standards.json index 328a668fdc85..d85bddf66df7 100644 --- a/src/data/standards.json +++ b/src/data/standards.json @@ -2,7 +2,7 @@ { "name": "standards.MailContacts.GeneralContact.Enabled", "cat": "Global", - "helpText": "", + "helpText": "Receives emails about updates about subscriptions etc", "addedComponent": { "type": "input", "name": "standards.MailContacts.GeneralContact.Mail", @@ -13,7 +13,7 @@ { "name": "standards.MailContacts.SecurityContact.Enabled", "cat": "Global", - "helpText": "", + "helpText": "Receives emails about security alerts or advisories by Microsoft", "addedComponent": { "type": "input", "name": "standards.MailContacts.SecurityContact.Mail", @@ -24,7 +24,7 @@ { "name": "standards.MailContacts.MarketingContact.Enabled", "cat": "Global", - "helpText": "", + "helpText": "Receives the emails related to marketing; new features etc", "addedComponent": { "type": "input", "name": "standards.MailContacts.MarketingContact.Mail", @@ -35,7 +35,7 @@ { "name": "standards.MailContacts.TechContact.Enabled", "cat": "Global", - "helpText": "", + "helpText": "Receives emails related to possible technical issues, service disruptions, etc", "addedComponent": { "type": "input", "name": "standards.MailContacts.TechContact.Mail", @@ -46,7 +46,7 @@ { "name": "standards.AuditLog", "cat": "Global", - "helpText": "", + "helpText": "Also runs Enable-OrganizationCustomization if needed", "addedComponent": null, "label": "Enable the Unified Audit Log" }, @@ -67,21 +67,21 @@ { "name": "standards.ModernAuth", "cat": "Global", - "helpText": "", + "helpText": "Modern Authentication is enabled by default. This standard is no longer required and can be safely disabled", "addedComponent": null, "label": "Enable Modern Authentication" }, { "name": "standards.DisableBasicAuth", "cat": "Global", - "helpText": "", + "helpText": "Basic Authentication is disabled by default. This standard is no longer required and can be safely disabled", "addedComponent": null, "label": "Disable Basic Authentication" }, { "name": "standards.DisableBasicAuthSMTP", "cat": "Global", - "helpText": "", + "helpText": "Disables SMTP AUTH for the organization. This is the default for new tenants. Can be overridden by enabling SMTP AUTH on specific users", "addedComponent": null, "label": "Disable SMTP Basic Authentication" }, @@ -95,14 +95,14 @@ { "name": "standards.PWnumberMatchingRequiredState", "cat": "AAD", - "helpText": "", + "helpText": "Passwordless with number matching is now enabled by default. This standard is no longer required and can be safely disabled", "addedComponent": null, "label": "Enable Passwordless with Number Matching" }, { "cat": "AAD", "name": "standards.PWdisplayAppInformationRequiredState", - "helpText": "", + "helpText": "Enables the MS authenticator app to display information about the app that is requesting authentication", "addedComponent": null, "label": "Enable Passwordless with Location information and Number Matching" }, @@ -151,21 +151,21 @@ { "cat": "AAD", "name": "standards.SecurityDefaults", - "helpText": "", + "helpText": "Enables security defaults for the tenant. This has a lot of implications and should be carefully considered before enabling", "addedComponent": null, "label": "Enable Security Defaults" }, { "cat": "AAD", "name": "standards.PasswordExpireDisabled", - "helpText": "", + "helpText": "Disables the expiration of passwords for the tenant", "addedComponent": null, "label": "Do not expire passwords" }, { "cat": "AAD", "name": "standards.DisableSecurityGroupUsers", - "helpText": "", + "helpText": "Completely disables the creation of security groups by users. This also breaks the ability to manage groups themselves, or create Teams", "addedComponent": null, "label": "Disable Security Group creation by users" }, @@ -210,14 +210,14 @@ { "cat": "AAD", "name": "standards.NudgeMFA.enable", - "helpText": "", + "helpText": "Enables registration campaign for the tenant", "addedComponent": null, "label": "Request to setup Authenticator if not setup yet." }, { "cat": "AAD", "name": "standards.NudgeMFA.disable", - "helpText": "", + "helpText": "Disables registration campaign for the tenant", "addedComponent": null, "label": "Disables the request to setup Authenticator if setup." }, @@ -238,7 +238,7 @@ { "cat": "AAD", "name": "standards.UndoOauth", - "helpText": "", + "helpText": "Disables App consent and set to Allow user consent for apps", "addedComponent": null, "label": "Undo App Consent Standard" }, @@ -252,7 +252,7 @@ { "cat": "AAD", "name": "standards.EnableFIDO2", - "helpText": "", + "helpText": "Enables the FIDO2 authenticationMethod for the tenant", "addedComponent": null, "label": "Enable FIDO2 capabilities" }, @@ -298,14 +298,14 @@ { "name": "standards.AutoExpandArchive", "cat": "Exchange", - "helpText": "", + "helpText": "Enables auto-expanding archives for the tenant", "addedComponent": null, "label": "Enable Auto-expanding archives" }, { "name": "standards.SpoofWarn.enable", "cat": "Exchange", - "helpText": "", + "helpText": "Adds indicators to e-mail messages received from external senders in Outlook. Works on all Outlook clients/OWA", "addedComponent": null, "label": "Enable Spoofing warnings for Outlook (This e-mail is external identifiers)" }, @@ -340,7 +340,7 @@ { "name": "standards.ActivityBasedTimeout", "cat": "Global", - "helpText": "", + "helpText": "Enables and sets Idle session timeout for Microsoft 365 to 1 hour. This policy affects most M365 web apps", "addedComponent": null, "label": "Enable 1 hour Activity based Timeout" }, @@ -371,12 +371,12 @@ "value": "Contributor" }, { - "label": "Free Busy Time And Subject And Location - The user can view free/busy time within the calendar and the subject and location of appointments.", - "value": "FreeBusyTimeAndSubjectAndLocation" + "label": "Limited Details - The user can view free/busy time within the calendar and the subject and location of appointments.", + "value": "LimitedDetails" }, { - "label": "Indicates that the user can view only free/busy time within the calendar.", - "value": "FreeBusyTimeOnly" + "label": "Availability Only - Indicates that the user can view only free/busy time within the calendar.", + "value": "AvailabilityOnly" }, { "label": "None - The user has no permissions on the folder.", @@ -414,7 +414,7 @@ { "name": "standards.intuneDeviceRetirementDays.Enabled", "cat": "Intune", - "helpText": "", + "helpText": "A value between 0 and 270 is supported. A value of 0 disables retirement.", "addedComponent": { "type": "input", "name": "standards.intuneDeviceRetirementDays.days", @@ -485,14 +485,14 @@ { "name": "standards.DeletedUserRentention", "cat": "SharePoint", - "helpText": "", + "helpText": "Sets the retenton period for deleted users OneDrive to 1 year/365 days", "addedComponent": null, "label": "Retain a deleted user OneDrive for 1 year" }, { "name": "standards.DisableUserSiteCreate", "cat": "SharePoint", - "helpText": "", + "helpText": "Disables users from creating new SharePoint sites", "addedComponent": null, "label": "Disable site creation by standard users" }, diff --git a/src/routes.js b/src/routes.js index d305063a4109..5be9247bfae0 100644 --- a/src/routes.js +++ b/src/routes.js @@ -225,6 +225,10 @@ const ServiceHealth = React.lazy(() => import('src/views/tenant/administration/S const EnterpriseApplications = React.lazy(() => import('src/views/tenant/administration/ListEnterpriseApps'), ) +const MailboxRestoreWizard = React.lazy(() => + import('src/views/email-exchange/tools/MailboxRestoreWizard'), +) +const MailboxRestores = React.lazy(() => import('src/views/email-exchange/tools/MailboxRestores')) const routes = [ // { path: '/', exact: true, name: 'Home' }, @@ -544,6 +548,16 @@ const routes = [ name: 'List Spamfilter Templates', component: SpamFilterTemplate, }, + { + path: '/email/tools/mailbox-restore-wizard', + name: 'Mailbox Restore Wizard', + component: MailboxRestoreWizard, + }, + { + path: '/email/tools/mailbox-restores', + name: 'Mailbox Restores', + component: MailboxRestores, + }, { path: '/email/spamfilter/add-template', name: 'Add Spamfilter Template', diff --git a/src/views/cipp/CIPPSettings.js b/src/views/cipp/CIPPSettings.js index 8c46d827521f..965ae5beef54 100644 --- a/src/views/cipp/CIPPSettings.js +++ b/src/views/cipp/CIPPSettings.js @@ -86,7 +86,6 @@ import { CellDelegatedPrivilege } from 'src/components/tables/CellDelegatedPrivi import { TableModalButton } from 'src/components/buttons' import { cellTableFormatter } from 'src/components/tables/CellTable' import { cellGenericFormatter } from 'src/components/tables/CellGenericFormat' -import { check } from 'prettier' const CIPPSettings = () => { const [active, setActive] = useState(1) @@ -151,9 +150,10 @@ const CIPPSettings = () => { export default CIPPSettings const GeneralSettings = () => { - const { data: versions, isSuccess: isSuccessVersion } = useLoadVersionsQuery() const { data: tenants = [] } = useListTenantsQuery({ AllTenantSelector: false }) const [checkPermissions, permissionsResult] = useLazyExecPermissionsAccessCheckQuery() + const [checkGDAP, GDAPResult] = useLazyGenericGetRequestQuery() + const [clearCache, clearCacheResult] = useLazyExecClearCacheQuery() const [checkAccess, accessCheckResult] = useLazyExecTenantsAccessCheckQuery() const [selectedTenants, setSelectedTenants] = useState([]) @@ -207,6 +207,43 @@ const GeneralSettings = () => { }, ] + const checkGDAPColumns = [ + { + name: 'Tenant', + selector: (row) => row['Tenant'], + sortable: true, + cell: cellGenericFormatter(), + minWidth: '200px', + maxWidth: '200px', + }, + { + name: 'Error Type', + selector: (row) => row['Type'], + sortable: true, + cell: cellGenericFormatter(), + minWidth: '100px', + maxWidth: '100px', + }, + { + name: 'Issue', + selector: (row) => row?.Issue, + sortable: true, + cell: cellGenericFormatter(), + }, + { + name: 'Resolution Link', + sortable: true, + selector: (row) => row?.Link, + cell: cellGenericFormatter(), + }, + { + name: 'Relationship ID', + sortable: true, + selector: (row) => row?.Relationship, + cell: cellGenericFormatter(), + }, + ] + const handleCheckAccess = () => { const mapped = tenants.reduce( (current, { customerId, ...rest }) => ({ @@ -287,21 +324,6 @@ const GeneralSettings = () => { return tokenOffcanvasGroups } - const handleClearCache = useConfirmModal({ - body:
Are you sure you want to clear the cache?
, - onConfirm: () => { - clearCache({ tenantsOnly: false }) - localStorage.clear() - }, - }) - - const handleClearCacheTenant = useConfirmModal({ - body:
Are you sure you want to clear the cache?
, - onConfirm: () => { - clearCache({ tenantsOnly: true }) - }, - }) - const tableProps = { pagination: false, actions: [ @@ -315,60 +337,17 @@ const GeneralSettings = () => { />, ], } - const downloadTxtFile = (data) => { - const txtdata = [JSON.stringify(RunBackupResult.data.backup)] - const file = new Blob(txtdata, { type: 'text/plain' }) - const element = document.createElement('a') - element.href = URL.createObjectURL(file) - element.download = 'CIPP-Backup' + Date.now() + '.json' - document.body.appendChild(element) - element.click() - } - const inputRef = useRef(null) - const handleChange = (e) => { - const fileReader = new FileReader() - fileReader.readAsText(e.target.files[0], 'UTF-8') - fileReader.onload = (e) => { - restoreBackup({ path: '/api/ExecRestoreBackup', values: e.target.result }) - } - } + return (
- - - Frontend Version - - - -
Latest: {isSuccessVersion ? versions.RemoteCIPPVersion : }
-
Current: {isSuccessVersion ? versions.LocalCIPPVersion : }
-
-
-
- - - - API Version - - - -
Latest: {isSuccessVersion ? versions.RemoteCIPPAPIVersion : }
-
Current: {isSuccessVersion ? versions.LocalCIPPAPIVersion : }
-
-
+
- - + + Permissions Check @@ -402,7 +381,7 @@ const GeneralSettings = () => { documentation on how to add permissions{' '} here @@ -414,23 +393,6 @@ const GeneralSettings = () => { )} - {permissionsResult.data.Results?.MissingGroups.length > 0 && ( - <> - Your SAM User is missing the following group memberships. - - {permissionsResult.data.Results?.MissingGroups?.map((r, index) => ( - {r} - ))} - - - )} - {permissionsResult.data.Results?.CIPPGroupCount == 0 && ( - <> - NOTE: Your M365 GDAP groups were not set up by CIPP. Please check the groups - below to see if you have the correct GDAP permissions, or execute an access - check. - - )} {permissionsResult.data.Results?.AccessTokenDetails?.Name !== '' && ( <> @@ -448,68 +410,70 @@ const GeneralSettings = () => { /> )} - {permissionsResult.data.Results?.Memberships !== '' && ( + + )} + + + + + + + GDAP Check + + + Click the button below to start a check for general GDAP settings. + checkGDAP({ path: '/api/ExecAccessChecks?GDAP=true' })} + disabled={GDAPResult.isFetching} + className="mt-3" + > + {GDAPResult.isFetching && ( + + )} + Run GDAP Check + + + + {GDAPResult.isSuccess && GDAPResult.data.Results.GDAPIssues?.length > 0 && ( + + )} + {GDAPResult.isSuccess && GDAPResult.data.Results.GDAPIssues?.length === 0 && ( + + No relationships with issues found. Please perform a Permissions Check or + Tenant Access Check if you are experiencing issues. + + )} + {GDAPResult.isSuccess && ( <> p['@odata.type'] == '#microsoft.graph.group', )} title="Groups" /> p['@odata.type'] == '#microsoft.graph.directoryRole', )} title="Roles" /> )} - - )} - - - - - - - Clear Cache - - - - Click the button below to clear the application cache. You can clear only the tenant - cache, or all caches. + - handleClearCache()} - disabled={clearCacheResult.isFetching} - className="me-3 mt-3" - > - {clearCacheResult.isFetching && ( - - )} - Clear All Caches - - handleClearCacheTenant()} - disabled={clearCacheResult.isFetching} - className="me-3 mt-3" - > - {clearCacheResult.isFetching && ( - - )} - Clear Tenant Cache - - {clearCacheResult.isSuccess && ( -
{clearCacheResult.data?.Results}
- )} - - + + Tenant Access Check @@ -539,6 +503,7 @@ const GeneralSettings = () => { )} + { - - -
) @@ -1357,9 +1319,26 @@ const DNSSettings = () => { const [getDnsConfig, getDnsConfigResult] = useLazyGetDnsConfigQuery() const [editDnsConfig, editDnsConfigResult] = useLazyEditDnsConfigQuery() const inputRef = useRef(null) + const [clearCache, clearCacheResult] = useLazyExecClearCacheQuery() + const { data: versions, isSuccess: isSuccessVersion } = useLoadVersionsQuery() const [alertVisible, setAlertVisible] = useState(false) - + const downloadTxtFile = (data) => { + const txtdata = [JSON.stringify(RunBackupResult.data.backup)] + const file = new Blob(txtdata, { type: 'text/plain' }) + const element = document.createElement('a') + element.href = URL.createObjectURL(file) + element.download = 'CIPP-Backup' + Date.now() + '.json' + document.body.appendChild(element) + element.click() + } + const handleChange = (e) => { + const fileReader = new FileReader() + fileReader.readAsText(e.target.files[0], 'UTF-8') + fileReader.onload = (e) => { + restoreBackup({ path: '/api/ExecRestoreBackup', values: e.target.result }) + } + } const switchResolver = (resolver) => { editDnsConfig({ resolver }) getDnsConfig() @@ -1368,7 +1347,20 @@ const DNSSettings = () => { setAlertVisible(false) }, 2000) } + const handleClearCache = useConfirmModal({ + body:
Are you sure you want to clear the cache?
, + onConfirm: () => { + clearCache({ tenantsOnly: false }) + localStorage.clear() + }, + }) + const handleClearCacheTenant = useConfirmModal({ + body:
Are you sure you want to clear the cache?
, + onConfirm: () => { + clearCache({ tenantsOnly: true }) + }, + }) const resolvers = ['Google', 'Cloudflare', 'Quad9'] return ( @@ -1380,81 +1372,129 @@ const DNSSettings = () => { Application Settings -

DNS Resolver

- - {resolvers.map((r, index) => ( + + + + + +

DNS Resolver

+ + {resolvers.map((r, index) => ( + switchResolver(r)} + color={r === getDnsConfigResult.data.Resolver ? 'primary' : 'secondary'} + key={index} + > + {r} + + ))} + + {(editDnsConfigResult.isSuccess || editDnsConfigResult.isError) && ( + + {editDnsConfigResult.isSuccess + ? editDnsConfigResult.data.Results + : 'Error setting resolver'} + + )} +
+ +

Frontend Version

+ +
Latest: {isSuccessVersion ? versions.RemoteCIPPVersion : }
+
Current: {isSuccessVersion ? versions.LocalCIPPVersion : }
+
+
+ + +

Clear Caches

switchResolver(r)} - color={r === getDnsConfigResult.data.Resolver ? 'primary' : 'secondary'} - key={index} + className="me-2 mb-2" + onClick={() => handleClearCache()} + disabled={clearCacheResult.isFetching} > - {r} + {clearCacheResult.isFetching && ( + + )} + Clear All Cache - ))} -
- {(editDnsConfigResult.isSuccess || editDnsConfigResult.isError) && ( - - {editDnsConfigResult.isSuccess - ? editDnsConfigResult.data.Results - : 'Error setting resolver'} - - )} - - - - -

Settings Backup

- runBackup({ path: '/api/ExecRunBackup' })} - disabled={RunBackupResult.isFetching} - className="me-3 mt-3" - > - {RunBackupResult.isFetching && ( - + handleClearCacheTenant()} + disabled={clearCacheResult.isFetching} + > + {clearCacheResult.isFetching && ( + + )} + Clear Tenant Cache + + {clearCacheResult.isSuccess && ( +
{clearCacheResult.data?.Results}
)} - Run backup -
- handleChange(e)} - /> - inputRef.current.click()} - disabled={restoreBackupResult.isFetching} - className="me-3 mt-3" - > - {restoreBackupResult.isFetching && ( - +
+ + +

Settings Backup

+ runBackup({ path: '/api/ExecRunBackup' })} + disabled={RunBackupResult.isFetching} + > + {RunBackupResult.isFetching && ( + + )} + Run backup + + handleChange(e)} + /> + inputRef.current.click()} + disabled={restoreBackupResult.isFetching} + > + {restoreBackupResult.isFetching && ( + + )} + Restore backup + + {restoreBackupResult.isSuccess && ( + <> + {restoreBackupResult.data.Results} + )} - Restore backup - - {restoreBackupResult.isSuccess && ( - <> - {restoreBackupResult.data.Results} - - )} - {RunBackupResult.isSuccess && ( - <> - - downloadTxtFile(RunBackupResult.data.backup)} - className="m-1" - > - Download Backup - - - - )} -
+ {RunBackupResult.isSuccess && ( + <> + + downloadTxtFile(RunBackupResult.data.backup)}> + Download Backup + + + + )} + + +

Backend API Version

+ +
Latest: {isSuccessVersion ? versions.RemoteCIPPAPIVersion : }
+
Current: {isSuccessVersion ? versions.LocalCIPPAPIVersion : }
+
+
)} diff --git a/src/views/cipp/Setup.js b/src/views/cipp/Setup.js index 82da760206e7..e8d9a33d3064 100644 --- a/src/views/cipp/Setup.js +++ b/src/views/cipp/Setup.js @@ -51,7 +51,7 @@ const Setup = () => { const valbutton = (value) => getResults.data?.step < 5 ? undefined - : `You must finish the setup process. you are currently at step ${getResults.data?.step} of 5.` + : `You do not have to click next. Finish the wizard via the setup button below. After it says "Setup Completed" you may browse away from this page.` const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery() const [genericGetRequest, getResults] = useLazyGenericGetRequestQuery() const onSubmit = (values) => { @@ -159,14 +159,14 @@ const Setup = () => { Please use a Global Administrator to perform these tasks. You can restart the process at any time, by clicking on the start button once more.

- + startCIPPSetup(true)} - validate={valbutton} + validate={() => valbutton()} > Start Setup Wizard diff --git a/src/views/email-exchange/tools/MailboxRestoreWizard.js b/src/views/email-exchange/tools/MailboxRestoreWizard.js new file mode 100644 index 000000000000..2e8acddc59f7 --- /dev/null +++ b/src/views/email-exchange/tools/MailboxRestoreWizard.js @@ -0,0 +1,214 @@ +import React from 'react' +import { CCallout, CCol, CListGroup, CListGroupItem, CRow, CSpinner } from '@coreui/react' +import { Field, FormSpy } from 'react-final-form' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { faExclamationTriangle, faTimes, faCheck } from '@fortawesome/free-solid-svg-icons' +import { useSelector } from 'react-redux' +import { CippWizard } from 'src/components/layout' +import PropTypes from 'prop-types' +import { + RFFCFormCheck, + RFFCFormInput, + RFFCFormSelect, + RFFCFormSwitch, + RFFSelectSearch, +} from 'src/components/forms' +import { TenantSelector } from 'src/components/utilities' +import { useListUsersQuery } from 'src/store/api/users' +import { useGenericGetRequestQuery, useLazyGenericPostRequestQuery } from 'src/store/api/app' + +const Error = ({ name }) => ( + + touched && error ? ( + + + {error} + + ) : null + } + /> +) + +Error.propTypes = { + name: PropTypes.string.isRequired, +} + +const MailboxRestoreWizard = () => { + const tenantDomain = useSelector((state) => state.app.currentTenant.defaultDomainName) + const { + data: sourceMailboxes = [], + isFetching: sMailboxesIsFetching, + error: sMailboxError, + } = useGenericGetRequestQuery({ + path: '/api/ListMailboxes', + params: { TenantFilter: tenantDomain, SoftDeletedMailbox: true }, + }) + const { + data: targetMailboxes = [], + isFetching: tMailboxesIsFetching, + error: tMailboxError, + } = useGenericGetRequestQuery({ + path: '/api/ListMailboxes', + params: { TenantFilter: tenantDomain }, + }) + const currentSettings = useSelector((state) => state.app) + const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery() + + const handleSubmit = async (values) => { + const shippedValues = { + TenantFilter: tenantDomain, + RequestName: values.RequestName, + SourceMailbox: values.SourceMailbox.value, + TargetMailbox: values.TargetMailbox.value, + BadItemLimit: values.BadItemLimit, + LargeItemLimit: values.LargeItemLimit, + AcceptLargeDataLoss: values.AcceptLargeDataLoss, + } + + //alert(JSON.stringify(values, null, 2)) + genericPostRequest({ path: '/api/ExecMailboxRestore', values: shippedValues }) + } + + return ( + + +
+

Step 1

+
Choose a tenant
+
+
+ {(props) => } +
+
+ +
+

Step 2

+
Select a soft deleted mailbox to restore.
+
+
+
+ ({ + value: mbx.ExchangeGuid, + name: `${mbx.displayName} <${mbx.UPN}>`, + }))} + placeholder={!sMailboxesIsFetching ? 'Select mailbox' : 'Loading...'} + name="SourceMailbox" + /> + {sMailboxError && Failed to load source mailboxes} +
+
+
+ +
+

Step 2

+
Select a mailbox to restore to.
+
+
+
+ ({ + value: mbx.ExchangeGuid, + name: `${mbx.displayName} <${mbx.UPN}>`, + }))} + placeholder={!tMailboxesIsFetching ? 'Select mailbox' : 'Loading...'} + name="TargetMailbox" + /> + {sMailboxError && Failed to load source mailboxes} +
+
+
+ +
+

Step 3

+
Enter Restore Request Options
+
+
+
+ + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+

Step 4

+
Confirm and apply
+
+
+
+ {postResults.isFetching && ( + + Loading + + )} + {postResults.isSuccess && ( + + {postResults.data.Results.map((message, idx) => { + return
  • {message}
  • + })} +
    + )} + {!postResults.isSuccess && ( + + {(props) => ( + <> + + + + +
    Selected Tenant:
    + {tenantDomain} +
    + +
    Source Mailbox:
    + {props.values.SourceMailbox.label} +
    + +
    Target Mailbox:
    + {props.values.TargetMailbox.label} +
    +
    +
    +
    + + )} +
    + )} +
    +
    +
    +
    + ) +} + +export default MailboxRestoreWizard diff --git a/src/views/email-exchange/tools/MailboxRestores.js b/src/views/email-exchange/tools/MailboxRestores.js new file mode 100644 index 000000000000..05459fa1e08d --- /dev/null +++ b/src/views/email-exchange/tools/MailboxRestores.js @@ -0,0 +1,170 @@ +import { CSpinner, CButton } from '@coreui/react' +import { + faEllipsisV, + faTrashAlt, + faExclamationTriangle, + faCheck, +} from '@fortawesome/free-solid-svg-icons' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import React, { useState } from 'react' +import { useSelector } from 'react-redux' +import { CippPageList } from 'src/components/layout' +import { + CellDelegatedPrivilege, + cellDateFormatter, + cellNullTextFormatter, +} from 'src/components/tables' +import { CippActionsOffcanvas } from 'src/components/utilities' +import GDAPRoles from 'src/data/GDAPRoles' +import { useLazyGenericGetRequestQuery } from 'src/store/api/app' +import { ModalService } from 'src/components/utilities' +import { constants } from 'buffer' +import Skeleton from 'react-loading-skeleton' +import { cellGenericFormatter } from 'src/components/tables/CellGenericFormat' + +const Actions = (row, rowIndex, formatExtraData) => { + const [ocVisible, setOCVisible] = useState(false) + const [getMailboxRestoreStats, mailboxRestoreStats] = useLazyGenericGetRequestQuery() + const tenant = useSelector((state) => state.app.currentTenant) + + function statProperty(mailboxRestoreStats, propertyName) { + return ( + <> + {mailboxRestoreStats.isFetching && } + {!mailboxRestoreStats.isFetching && + mailboxRestoreStats.isSuccess && + (mailboxRestoreStats?.data[0][propertyName]?.toString() ?? ' ')} + + ) + } + + function loadOffCanvasDetails(id) { + setOCVisible(true) + getMailboxRestoreStats({ + path: 'api/ListMailboxRestores', + params: { + TenantFilter: tenant.defaultDomainName, + Identity: id, + Statistics: true, + IncludeReport: true, + }, + }) + } + var extendedInfo = [ + { label: 'Status', value: statProperty(mailboxRestoreStats, 'Status') }, + { label: 'Status Detail', value: statProperty(mailboxRestoreStats, 'StatusDetail') }, + { label: 'Sync Stage', value: statProperty(mailboxRestoreStats, 'SyncStage') }, + { label: 'Data Consistency', value: statProperty(mailboxRestoreStats, 'DataConsistencyScore') }, + { + label: 'Estimated Transfer', + value: statProperty(mailboxRestoreStats, 'EstimatedTransferSize'), + }, + { + label: 'Bytes Transferred', + value: statProperty(mailboxRestoreStats, 'BytesTransferred'), + }, + { + label: 'Percent Complete', + value: statProperty(mailboxRestoreStats, 'PercentComplete'), + }, + { + label: 'Estimated Item Count', + value: statProperty(mailboxRestoreStats, 'EstimatedTransferItemCount'), + }, + { + label: 'Transferred Items', + value: statProperty(mailboxRestoreStats, 'ItemsTransferred'), + }, + ] + + return ( + <> + loadOffCanvasDetails(row.Identity)}> + + + setOCVisible(false)} + /> + + ) +} + +const MailboxRestores = () => { + const tenant = useSelector((state) => state.app.currentTenant) + const columns = [ + { + name: 'Name', + selector: (row) => row['Name'], + sortable: true, + exportSelector: 'Name', + cell: cellGenericFormatter(), + }, + { + name: 'Status', + selector: (row) => row['Status'], + sortable: true, + exportSelector: 'Status', + cell: cellGenericFormatter(), + }, + { + name: 'Target Mailbox', + selector: (row) => row['TargetMailbox'], + sortable: true, + exportSelector: 'TargetMailbox', + cell: cellGenericFormatter(), + }, + { + name: 'Created', + selector: (row) => row['WhenCreated'], + sortable: true, + exportSelector: 'WhenCreated', + cell: cellDateFormatter({ format: 'short' }), + }, + { + name: 'Changed', + selector: (row) => row['WhenChanged'], + sortable: true, + exportSelector: 'WhenChanged', + cell: cellDateFormatter({ format: 'short' }), + }, + { + name: 'Actions', + cell: Actions, + maxWidth: '80px', + }, + ] + return ( +
    + +
    + ) +} + +export default MailboxRestores diff --git a/src/views/endpoint/intune/MEMAddPolicy.js b/src/views/endpoint/intune/MEMAddPolicy.js index f87f6b87dd2a..a564ce3d36db 100644 --- a/src/views/endpoint/intune/MEMAddPolicy.js +++ b/src/views/endpoint/intune/MEMAddPolicy.js @@ -130,7 +130,9 @@ const AddPolicy = () => {

    Step 2

    Enter the raw JSON for this policy. See{' '} - this for more + + this + {' '} information.
    diff --git a/src/views/identity/administration/UserActions.js b/src/views/identity/administration/UserActions.js index 2a96415e0b15..774d27e481c3 100644 --- a/src/views/identity/administration/UserActions.js +++ b/src/views/identity/administration/UserActions.js @@ -31,7 +31,7 @@ export default function UserActions({ tenantDomain, userId, userEmail, className } const editLink = `/identity/administration/users/edit?tenantDomain=${tenantDomain}&userId=${userId}` - const editMailboxLink = `/email/administration/edit-mailbox-permissions?tenantDomain=${tenantDomain}&userId=${userId}` + const editMailboxLink = `/email/administration/edit-mailbox-permissions?tenantDomain=${tenantDomain}&userId=${userEmail}` const actions = [ { diff --git a/src/views/tenant/administration/GDAPWizard.js b/src/views/tenant/administration/GDAPWizard.js index 8722bde0b5f7..3c6147e7547d 100644 --- a/src/views/tenant/administration/GDAPWizard.js +++ b/src/views/tenant/administration/GDAPWizard.js @@ -54,11 +54,7 @@ const GDAPWizard = () => {
    The GDAP migration tool requires setup. Please check the documentation{' '} - + here.

    diff --git a/src/views/tenant/administration/ListGDAPRelationships.js b/src/views/tenant/administration/ListGDAPRelationships.js index dcba0648a907..6ee6a8cfa07c 100644 --- a/src/views/tenant/administration/ListGDAPRelationships.js +++ b/src/views/tenant/administration/ListGDAPRelationships.js @@ -163,6 +163,17 @@ const GDAPRelationships = () => { exportSelector: 'endDateTime', cell: (row) => (row['autoExtendDuration'] === 'PT0S' ? 'No' : 'Yes'), }, + { + name: 'Includes CA Role', + selector: (row) => row?.accessDetails, + sortable: true, + cell: (row) => + row?.accessDetails?.unifiedRoles?.filter( + (e) => e.roleDefinitionId === '62e90394-69f5-4237-9190-012177145e10', + ).length > 0 + ? 'Yes' + : 'No', + }, { name: 'Actions', cell: Actions, @@ -178,9 +189,17 @@ const GDAPRelationships = () => { tenantSelector={false} datatable={{ filterlist: [ - { filterName: 'Active Relationships', filter: '"status":"active"' }, - { filterName: 'Terminated Relationships', filter: '"status":"Terminated"' }, + { filterName: 'Active Relationships', filter: 'Complex: status eq active' }, + { filterName: 'Terminated Relationships', filter: 'Complex: status eq terminated' }, { filterName: 'Pending Relationships', filter: 'Pending' }, + { + filterName: 'Active with Auto Extend', + filter: 'Complex: status eq active; autoExtendDuration ne PT0S', + }, + { + filterName: 'Active without Auto Extend', + filter: 'Complex: status eq active; autoExtendDuration eq PT0S', + }, ], tableProps: { selectableRows: true, diff --git a/src/views/tenant/standards/ApplyStandard.js b/src/views/tenant/standards/ApplyStandard.js index 64d9fbf0ffce..2f84f99607ea 100644 --- a/src/views/tenant/standards/ApplyStandard.js +++ b/src/views/tenant/standards/ApplyStandard.js @@ -83,7 +83,7 @@ const ApplyStandard = () => { > Ensure you read{' '} - + the documentation fully {' '} before proceeding with this wizard. Some of the changes cannot be reverted by CIPP. diff --git a/src/views/tenant/standards/ListAppliedStandards.js b/src/views/tenant/standards/ListAppliedStandards.js index d22f03d05862..ed0c7856a40a 100644 --- a/src/views/tenant/standards/ListAppliedStandards.js +++ b/src/views/tenant/standards/ListAppliedStandards.js @@ -514,7 +514,7 @@ const ListAppliedStandards = () => { name: template.name, }))} placeholder="Select a template" - label="Choose your Transport Rule templates to apply" + label="Choose your Exchange Connector templates to apply" /> )} diff --git a/version_latest.txt b/version_latest.txt index 64b5ae3938a0..50021202769b 100644 --- a/version_latest.txt +++ b/version_latest.txt @@ -1 +1 @@ -4.4.0 \ No newline at end of file +4.5.5 \ No newline at end of file