diff --git a/package.json b/package.json index 039c0b97dd85..bdf502e059b8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cipp", - "version": "5.9.3", + "version": "6.0.2", "description": "The CyberDrain Improved Partner Portal is a portal to help manage administration for Microsoft Partners.", "homepage": "https://cipp.app/", "bugs": { diff --git a/public/version_latest.txt b/public/version_latest.txt index 99a8b57b6f85..9b9a244206f6 100644 --- a/public/version_latest.txt +++ b/public/version_latest.txt @@ -1 +1 @@ -5.9.3 +6.0.2 diff --git a/src/_nav.jsx b/src/_nav.jsx index 631d6eaf1169..0ab9f9db7c5b 100644 --- a/src/_nav.jsx +++ b/src/_nav.jsx @@ -821,6 +821,11 @@ const _nav = [ name: 'Extensions Settings', to: '/cipp/extensions', }, + { + component: CNavItem, + name: 'Extension Sync', + to: '/cipp/extension-sync', + }, { component: CNavItem, name: 'User Settings', diff --git a/src/components/tables/CellBytes.jsx b/src/components/tables/CellBytes.jsx new file mode 100644 index 000000000000..b5b3e38211b8 --- /dev/null +++ b/src/components/tables/CellBytes.jsx @@ -0,0 +1,24 @@ +import PropTypes from 'prop-types' + +export function CellBytes({ cell }) { + return (cell / 1024 ** 3).toFixed(2) +} + +CellBytes.propTypes = { + propName: PropTypes.string, + cell: PropTypes.object, +} + +export function CellBytesToPercentage({ row, value, dividedBy }) { + return Math.round((row[value] / row[dividedBy]) * 100 * 10) / 10 +} + +CellBytesToPercentage.propTypes = { + propName: PropTypes.string, + cell: PropTypes.object, +} + +export const cellBytesFormatter = () => (row, index, column, id) => { + const cell = column.selector(row) + return CellBytes({ cell }) +} diff --git a/src/components/tables/CellCopyButton.jsx b/src/components/tables/CellCopyButton.jsx index eeed76d73478..ca3a2b7c98c2 100644 --- a/src/components/tables/CellCopyButton.jsx +++ b/src/components/tables/CellCopyButton.jsx @@ -2,7 +2,6 @@ import PropTypes from 'prop-types' import CippCopyToClipboard from '../utilities/CippCopyToClipboard' export function CellCopyButton({ cell }) { - console.log('hi! cell:', cell) return } @@ -13,6 +12,5 @@ CellCopyButton.propTypes = { export const cellCopyButtonFormatter = () => (row, index, column, id) => { const cell = column.selector(row) - console.log('cell:', cell) return CellCopyButton({ cell }) } diff --git a/src/components/tables/CippTable.jsx b/src/components/tables/CippTable.jsx index 6eee3a54e611..04ede5a4c204 100644 --- a/src/components/tables/CippTable.jsx +++ b/src/components/tables/CippTable.jsx @@ -629,12 +629,14 @@ export default function CippTable({ if (!disablePDFExport || !disableCSVExport) { const keys = [] const exportFormatter = {} + const exportFormatterArgs = {} columns.map((col) => { if (col.exportSelector) keys.push(col.exportSelector) if (col.exportFormatter) exportFormatter[col.exportSelector] = col.exportFormatter + if (col.exportFormatterArgs) + exportFormatterArgs[col.exportSelector] = col.exportFormatterArgs return null }) - // Define the flatten function const flatten = (obj, prefix = '') => { if (obj === null) return {} @@ -664,18 +666,18 @@ export default function CippTable({ // Define the applyFormatter function const applyFormatter = (obj) => { return Object.keys(obj).reduce((acc, key) => { + const formatterArgs = exportFormatterArgs[key] const formatter = exportFormatter[key] - // Since the keys after flattening will be dot-separated, we need to adjust this to support nested keys if necessary. const keyParts = key.split('.') const finalKeyPart = keyParts[keyParts.length - 1] const formattedValue = - typeof formatter === 'function' ? formatter({ cell: obj[key] }) : obj[key] + typeof formatter === 'function' + ? formatter({ row: obj, cell: obj[key], ...formatterArgs }) + : obj[key] acc[key] = formattedValue return acc }, {}) } - - // Process exportData function const processExportData = (exportData, selectedColumns) => { //filter out the columns that are not selected via selectedColumns exportData = exportData.map((item) => { diff --git a/src/importsMap.jsx b/src/importsMap.jsx index 9666488abfc9..e6a8a422c777 100644 --- a/src/importsMap.jsx +++ b/src/importsMap.jsx @@ -137,6 +137,7 @@ import React from 'react' "/license": React.lazy(() => import('./views/pages/license/License')), "/cipp/settings": React.lazy(() => import('./views/cipp/app-settings/CIPPSettings')), "/cipp/extensions": React.lazy(() => import('./views/cipp/Extensions')), + "/cipp/extension-sync": React.lazy(() => import('./views/cipp/ExtensionSync')), "/cipp/setup": React.lazy(() => import('./views/cipp/Setup')), "/tenant/administration/securescore": React.lazy(() => import('./views/tenant/administration/SecureScore')), "/tenant/administration/gdap": React.lazy(() => import('./views/tenant/administration/GDAPWizard')), diff --git a/src/routes.json b/src/routes.json index 96584165d77c..b6a54db32b03 100644 --- a/src/routes.json +++ b/src/routes.json @@ -938,6 +938,12 @@ "component": "views/cipp/Extensions", "allowedRoles": ["admin"] }, + { + "path": "/cipp/extension-sync", + "name": "Extension Sync", + "component": "views/cipp/ExtensionSync", + "allowedRoles": ["admin"] + }, { "path": "/cipp/setup", "name": "Setup", diff --git a/src/views/cipp/ExtensionMappings.jsx b/src/views/cipp/ExtensionMappings.jsx index b493dce041a2..4de976555ccc 100644 --- a/src/views/cipp/ExtensionMappings.jsx +++ b/src/views/cipp/ExtensionMappings.jsx @@ -226,9 +226,7 @@ export default function ExtensionMappings({ type, fieldMappings = false, autoMap name: tenant.displayName, value: tenant.customerId, }))} - onChange={(e) => { - setMappingArray(e.value) - }} + onChange={(e) => setTenantMappingsArray(e.value)} isLoading={listMappingBackendResult.isFetching} /> @@ -238,16 +236,10 @@ export default function ExtensionMappings({ type, fieldMappings = false, autoMap { - return !Object.values(listMappingBackendResult.data?.Mappings) - .map((value) => { - return value.value - }) - .includes(client.value.toString()) - }).map((client) => ({ + values={listMappingBackendResult.data?.Companies.map((client) => ({ name: client.name, value: client.value, - }))} + })).sort((a, b) => a.name.localeCompare(b.name))} onChange={(e) => setMappingValue(e)} placeholder={`Select a ${type} Organization`} isLoading={listMappingBackendResult.isFetching} @@ -267,7 +259,7 @@ export default function ExtensionMappings({ type, fieldMappings = false, autoMap ...mappingArray, { Tenant: listMappingBackendResult.data?.Tenants.find( - (tenant) => tenant.customerId === mappingArray, + (tenant) => tenant.customerId === tenantMappingArray, ), companyName: mappingValue.label, companyId: mappingValue.value, diff --git a/src/views/cipp/ExtensionSync.jsx b/src/views/cipp/ExtensionSync.jsx new file mode 100644 index 000000000000..c9937921aa86 --- /dev/null +++ b/src/views/cipp/ExtensionSync.jsx @@ -0,0 +1,97 @@ +import React, { useState } from 'react' +import { CCol, CRow } from '@coreui/react' +import { useSelector } from 'react-redux' + +import { useLazyGenericGetRequestQuery, useLazyGenericPostRequestQuery } from 'src/store/api/app' + +import { CippPage, CippPageList } from 'src/components/layout' +import { CellTip, cellGenericFormatter } from 'src/components/tables/CellGenericFormat' +import 'react-datepicker/dist/react-datepicker.css' +import { CellBadge, cellBadgeFormatter, cellDateFormatter } from 'src/components/tables' +import { TitleButton } from 'src/components/buttons' + +const ExtensionSync = () => { + const [ExecuteGetRequest, getResults] = useLazyGenericGetRequestQuery() + const tenantDomain = useSelector((state) => state.app.currentTenant.defaultDomainName) + const [refreshState, setRefreshState] = useState(false) + const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery() + + const columns = [ + { + name: 'Tenant', + selector: (row) => row['Tenant'], + sortable: true, + cell: cellGenericFormatter(), + exportSelector: 'Tenants', + }, + { + name: 'Sync Type', + selector: (row) => row['SyncType'], + sortable: true, + cell: cellBadgeFormatter({ color: 'info' }), + exportSelector: 'SyncType', + }, + { + name: 'Task', + selector: (row) => row['Name'], + sortable: true, + cell: cellGenericFormatter(), + exportSelector: 'Name', + }, + { + name: 'Scheduled Time', + selector: (row) => row['ScheduledTime'], + sortable: true, + cell: cellDateFormatter({ format: 'short' }), + exportSelector: 'ScheduledTime', + }, + { + name: 'Last Run', + selector: (row) => row['ExecutedTime'], + sortable: true, + cell: cellDateFormatter({ format: 'short' }), + exportSelector: 'ExecutedTime', + }, + { + name: 'Repeats every', + selector: (row) => row['RepeatsEvery'], + sortable: true, + cell: (row) => CellTip(row['RepeatsEvery']), + exportSelector: 'RepeatsEvery', + }, + { + name: 'Results', + selector: (row) => row['Results'], + sortable: true, + cell: cellGenericFormatter(), + exportSelector: 'Results', + }, + ] + + return ( + + <> + + + + + + + + ) +} + +export default ExtensionSync diff --git a/src/views/cipp/Extensions.jsx b/src/views/cipp/Extensions.jsx index 8c8c9dd79dee..eec80f5a1068 100644 --- a/src/views/cipp/Extensions.jsx +++ b/src/views/cipp/Extensions.jsx @@ -67,6 +67,7 @@ export default function CIPPExtensions() { path: 'api/ExecExtensionSync?Extension=' + integrationType, }) } + disabled={disabled} className="me-2" > )} + {listSyncExtensionResult?.data?.Results && ( + + {listSyncExtensionResult?.data?.Results} + + )} {integration.mappingRequired && ( diff --git a/src/views/email-exchange/reports/MailboxStatisticsList.jsx b/src/views/email-exchange/reports/MailboxStatisticsList.jsx index 0a6a4c80e9c0..abe707575e83 100644 --- a/src/views/email-exchange/reports/MailboxStatisticsList.jsx +++ b/src/views/email-exchange/reports/MailboxStatisticsList.jsx @@ -2,6 +2,11 @@ import React, { useEffect, useState } from 'react' import { useSelector } from 'react-redux' import { CellTip, cellBooleanFormatter } from 'src/components/tables' import { CippPageList } from 'src/components/layout' +import { + CellBytes, + CellBytesToPercentage, + cellBytesFormatter, +} from 'src/components/tables/CellBytes' const MailboxStatsList = () => { const [tenantColumnSet, setTenantColumn] = useState(true) @@ -64,23 +69,32 @@ const MailboxStatsList = () => { exportSelector: 'lastActivityDate', }, { - selector: (row) => (row['storageUsedInBytes'] / 1024 ** 3).toFixed(2), + selector: (row) => row['storageUsedInBytes'], + cell: cellBytesFormatter(), name: 'Used Space (GB)', sortable: true, exportSelector: 'storageUsedInBytes', + exportFormatter: CellBytes, }, { - selector: (row) => (row['prohibitSendReceiveQuotaInBytes'] / 1024 ** 3).toFixed(2), + selector: (row) => row['prohibitSendReceiveQuotaInBytes'], + cell: cellBytesFormatter(), name: 'Quota (GB)', sortable: true, - exportSelector: 'QuotaGB', + exportSelector: 'prohibitSendReceiveQuotaInBytes', + exportFormatter: CellBytes, }, { selector: (row) => Math.round((row.storageUsedInBytes / row.prohibitSendReceiveQuotaInBytes) * 100 * 10) / 10, name: 'Quota Used(%)', sortable: true, - exportSelector: 'QuotaUsed', + exportSelector: 'CippStatus', + exportFormatter: CellBytesToPercentage, + exportFormatterArgs: { + value: 'storageUsedInBytes', + dividedBy: 'prohibitSendReceiveQuotaInBytes', + }, }, { selector: (row) => row['itemCount'], diff --git a/src/views/home/Home.jsx b/src/views/home/Home.jsx index 7111f9e41bb2..c3dc3de2afa6 100644 --- a/src/views/home/Home.jsx +++ b/src/views/home/Home.jsx @@ -289,7 +289,7 @@ const TenantDashboard = () => { {organization.verifiedDomains?.slice(0, 3).map((item, idx) => (
  • {item.name}
  • ))} - {organization.verifiedDomains?.length > 5 && ( + {organization.verifiedDomains?.length > 3 && ( <> {organization.verifiedDomains?.slice(3).map((item, idx) => ( diff --git a/src/views/identity/administration/DeployJITAdmin.jsx b/src/views/identity/administration/DeployJITAdmin.jsx index 9d16549f9867..81d23666fa61 100644 --- a/src/views/identity/administration/DeployJITAdmin.jsx +++ b/src/views/identity/administration/DeployJITAdmin.jsx @@ -8,6 +8,7 @@ import { RFFCFormRadioList, RFFCFormSwitch, RFFSelectSearch, + RFFCFormSelect, } from 'src/components/forms' import { useGenericGetRequestQuery, @@ -26,6 +27,7 @@ import 'react-datepicker/dist/react-datepicker.css' import { useListUsersQuery } from 'src/store/api/users' import GDAPRoles from 'src/data/GDAPRoles' import { CippDatatable, cellDateFormatter } from 'src/components/tables' +import { useListDomainsQuery } from 'src/store/api/domains' const DeployJITAdmin = () => { const [ExecuteGetRequest, getResults] = useLazyGenericGetRequestQuery() @@ -36,6 +38,11 @@ const DeployJITAdmin = () => { const tenantDomain = useSelector((state) => state.app.currentTenant.defaultDomainName) const [refreshState, setRefreshState] = useState(false) const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery() + const { + data: domains = [], + isFetching: domainsIsFetching, + error: domainsError, + } = useListDomainsQuery({ tenantDomain }) const onSubmit = (values) => { const startTime = Math.floor(startDate.getTime() / 1000) @@ -43,7 +50,7 @@ const DeployJITAdmin = () => { const shippedValues = { TenantFilter: tenantDomain, UserId: values.UserId?.value, - UserPrincipalName: values.UserPrincipalName, + UserPrincipalName: `${values.username}@${values.domain}`, FirstName: values.FirstName, LastName: values.LastName, useraction: values.useraction, @@ -134,8 +141,24 @@ const DeployJITAdmin = () => { + + + - + {domainsIsFetching && } + {!domainsIsFetching && ( + ({ + value: domain.id, + label: domain.id, + }))} + /> + )} + {domainsError && Failed to load list of domains} diff --git a/version_latest.txt b/version_latest.txt index 99a8b57b6f85..9b9a244206f6 100644 --- a/version_latest.txt +++ b/version_latest.txt @@ -1 +1 @@ -5.9.3 +6.0.2