From e234b600b24bd98d4929818217421b5b3155609b Mon Sep 17 00:00:00 2001 From: Teh-STIG <105899123+Teh-STIG@users.noreply.github.com> Date: Mon, 13 Jan 2025 13:08:45 -0600 Subject: [PATCH 01/98] Update Extensions.json (CloudFlare ZTNA Tunnel Integration) Moved "switch" from Hudu Integration tab/page to CloudFlare tab/page for enabling the Hudu through CloudFlare ZTNA Tunnel process. Added link from Hudu on how to configure self-hosted Hudu instance behind CloudFlare ZTNA Tunnel. Removed "Test" button from CloudFlare Integration tab as it doesn't do anything --- src/data/Extensions.json | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/data/Extensions.json b/src/data/Extensions.json index 7bdb55f719d2..bf70ef5b6a83 100644 --- a/src/data/Extensions.json +++ b/src/data/Extensions.json @@ -423,5 +423,45 @@ } ], "mappingRequired": false + }, + { + "name": "CloudFlare ZTNA Tunnel", + "id": "CTZTNA", + "type": "ztna", + "cat": "Zero Trust Network Tunnel", + "logo": "/assets/integrations/cloudflare.png", + "forceSyncButton": false, + "hideTestButton": true, + "description": "Enter your CloudFlare ZTNA Tunnel API Credentials to use with other Integrations", + "helpText": "These credentials can be used to allow traffic through a CloudFlare ZTNA Tunnel if your other Integrations are protected behind one. You need to select 'Behind a CF-ZTNA Tunnel' within the other Integration's settings to enable.", + "links": [ + { + "name": "CloudFlare ZTNA Tunnel Information", + "url": "https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/" + }, + { + "name": "Installing Hudu behind Cloudflare Zero Trust Tunnel", + "url": "https://support.hudu.com/hc/en-us/articles/13929358997399-Installing-Hudu-behind-Cloudflare-Zero-Trust-Tunnel" + } + ], + "SettingOptions": [ + { + "type": "password", + "name": "CloudFlareClientID.APIKey", + "label": "CloudFlare Tunnel Service Account Client ID" + }, + { + "type": "password", + "name": "CloudFlareAPIKey.APIKey", + "label": "CloudFlare Tunnel Service Account Client Secret" + }, + { + "_comment": "I have added this switch as a logic check for the Hudu integration script to check against when CIPP first connects to the Hudu Instance via Connect-HuduAPI.ps1", + "type": "switch", + "name": "Hudu.CFEnabled", + "label": " Connect to HUDU through CloudFlare Tunnel with the above Service Account credentials." + } + ], + "mappingRequired": false } ] From 1366c5e7f7826c132dfabb3bfc69065122b6f3a2 Mon Sep 17 00:00:00 2001 From: Teh-STIG <105899123+Teh-STIG@users.noreply.github.com> Date: Mon, 13 Jan 2025 13:09:38 -0600 Subject: [PATCH 02/98] Add files via upload Uploaded new CloudFlare logo that is longer and contains text per JD's recommendation. --- public/assets/integrations/cloudflare.png | Bin 0 -> 3712 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 public/assets/integrations/cloudflare.png diff --git a/public/assets/integrations/cloudflare.png b/public/assets/integrations/cloudflare.png new file mode 100644 index 0000000000000000000000000000000000000000..636ae68314054771927deefe99ec0adb3bb5817f GIT binary patch literal 3712 zcmV-`4uA1dNk&F^4gdgGMM6+kP&iC$4gdfz&%!$pr^6tUBqw{R>Lnl_3AjQ+zFRUD z7`1IA$kDP}{#f>Px;kzoNs+2y8EjbF*8dOh4b(N`*tVKHGMKGEcWy1kQf$hu>>`n2 z9RT2>0%pJj*Z~J%1+1%M$r0(nDnS&i-Pk5zCzb)yVW(dV@sB=Aw3~+#U8ydmmgrM5 zDSx?SQn4i&?w<=2F;VR)3Ne?YxhN$WiVCPyBVYu~fa%-#sTRhf-kQ!ty;UxiiD0S#Q$jU=hT8)${geTdbQH90 z!!+d&dw&EXLSfrBNXNW8^iNEBBh74p=LF>ov&SmW6l`si(rDQa&YHBz7Qhe@N@f2` z>B$7@sEqo%RieJ=Ezhk)A|1|;V?joV$S;nRlqAsCnNel0S|J~Ttj*IS%lC% zNbL~!$E^6hdhe$otIkuJ;r9AEon=(%PsRHkhf+A1>8*J8d0NUP_~L`+X^=?TkNC)A zVuaDfQN?2rJElcdr=83x^P;+a zDR4|%RQu3L8#kirGbd{%)S#avO^SLP#EenYW!uRZH=;h(NEnx+PQ8;a&ZAzvlP;4- z-FhcmCe*NZlEwKtf_HModDOFaQpI`H^=M>@8ByQbi4;Yhw-O~qy$|vvLEV?q#1*xl zJ6U4J`h&`n7B)CYlCrSFAV<#L5=J9NTG-+sMase+gA6g=B1Rd~!zOzPqOeMD1W0Qa zNaaU>ZL0Vg@!RKsXN`H}$>w-e@VAI50hK@B8kd>&n7boiLa@-sM@cJ+F90d8*of1-^PEh|6mwqN?`|SDol_ z0DMe7+RZuY0($0nX$4fHr@&7MJ??>DQ$jWRoO9C~JcDW?!5yX6ONzo@(IVWun zdfo&Oxu&0P*u*3mXc|KF3IWlAN3JcXx`LQ#0(8$IaR#!UL0pdl-Lgk{D(HFw@y8TI z3*NXKLD*}MSfxM*&M>c_YzQ(v5_H8E*Aaxh&4AoC3469UfU++>NcKq34NsUmD0>F- zOC-Fo#3ct|A2N{SUkY@?5T!2&d+9==PM?CFA7T*p*oUN@J^{Moh8T3+_@H8yfN#t& zfvWfa*PtltXr~KSh(Xi)qa3KrTDm=Q!o?Hh_2?=9<(+J!r_&1~oO9UftIUIQnYm2t z0JLV-+G1rspY|4uu~NF`gA7&tDn2-u8Lnlz@NRXOwYFTD&%3?tgr3;owW#117e1y= z{WcL7ltl&KPT|9P8(s2%$*X7rAG9wXctfMYZuHCoS5)&u_%U$`J2%of2b?NO;^V8t zgL9d=jLbL8GHY$IGM_hki^UL?Tr)rdJHLOGd3ql4YVGUdu}x(@Z!H#!G3vQ%{*1RY z6>kqhS2bJiqW=5l?@&2r7HA{w#kl0fFUR5YS*E@X0oG?E`ZH2H6o0%423eWQ0E)PcZ3`agxtm0htRKcM!D*`8pmXA1;mo;RqS4IXEC`d?j8KM0$t zfNC))p#CkS4&S5nB|#X%Hrnd_a6FPL*hjt90OH4kFLn5~-P=PHB zWx{J+_faOi)|D6l=58ssX=t$luwBeDhN()&5Q+_WFYX?!!h3P40iam2$z<3O0=h)Y zVJrai{J|!4ggEmZoCtB|^Z<%Wv;&+HBa9ZS4QJSWVr}R*G7#vFo(u%K69hQYtg;+Y z!fcTmj~Yxnazp;2RRlXEZmlBNAqj#a3AVsm*CqlU60qU0|a+k?6L)qq6NJInY?4n;1{I9yHhLKX6gdOzY9x3|l$dq+!Ko zGi>E-lZJJB+9tzDrRnL6gO1yow4%I?VaI0Dit;vweSg{}4K9r`sFgA!(eGw~JcUU+ zZANB+JcUU+Z6<&k!;waVQZfZO4t6&utt)#a;U_L9tt)#a;U@uf)1D-1pk^VBBpakD ze3{ncGDuVSGOee8qs=7Rl&QKYuqYfCd3TS_6UbXV8rj7Km>J5trWNjG?49XI&#mHpe4M0r z5-eIvoCWIEFBhrCB$Q$s0wK@u_opAvzg_n{9Bn?(lbEi(Ghuvuh)Zakg#&@Sv@rx!2Rt5}5 zX%&n)I$9az!rJE7c`_#7j|Snjvlx@_ zqU?^v^9m=x>F&j=G0jW#@YEQp1m(MX((?jSqNL{qrsLBcTGfbTO`D8M&I{;CgWZRW z@Rr6&MtE60T4*^UTN9^T1Nj0{X|UVSHbXl#Qrc!{TfOeEs>Y*e;xv#CV_mdy0X2py zx1ko;)@bPpY^z_HL0LS8q|PQ)WqwGo3+PG1b03<6+8Qy5*&L+KhhZ8MZpL)@VZ ziADX<^r@sr*$-O3f}S)yH=^pdxd;=GD(km&9r?9HHPVKMpb2< z<*V@=O~aI*fL#EpD(ifIHJ+pCWnH%S{AL!*`U>`k-Mua-Mr}GY3s`7Ic7DXjjl~&N eXxfa Date: Fri, 17 Jan 2025 12:41:34 +0100 Subject: [PATCH 03/98] Add app certificate alert --- src/data/alerts.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/data/alerts.json b/src/data/alerts.json index 0648464c74ff..f6d2b6074a97 100644 --- a/src/data/alerts.json +++ b/src/data/alerts.json @@ -80,6 +80,11 @@ "label": "Alert on expiring application secrets", "recommendedRunInterval": "1d" }, + { + "name": "AppCertificateExpiry ", + "label": "Alert on expiring application certificates", + "recommendedRunInterval": "1d" + }, { "name": "ApnCertExpiry", "label": "Alert on expiring APN certificates", From 4c7b56466823e1f4d32694c468f79363c0483309 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Fri, 17 Jan 2025 21:02:03 +0100 Subject: [PATCH 04/98] Add support for all CippFormComponent type in alerts input --- src/pages/tenant/administration/alert-configuration/alert.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/tenant/administration/alert-configuration/alert.jsx b/src/pages/tenant/administration/alert-configuration/alert.jsx index ab3d4d6130ae..4d9da5e9876d 100644 --- a/src/pages/tenant/administration/alert-configuration/alert.jsx +++ b/src/pages/tenant/administration/alert-configuration/alert.jsx @@ -512,7 +512,7 @@ const AlertWizard = () => { {commandValue?.value?.requiresInput && ( Date: Fri, 17 Jan 2025 21:02:08 +0100 Subject: [PATCH 05/98] input type and add option for InactiveLicensedUsers --- src/data/alerts.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/data/alerts.json b/src/data/alerts.json index e503a0bbf800..2fd99afb82b4 100644 --- a/src/data/alerts.json +++ b/src/data/alerts.json @@ -22,12 +22,17 @@ { "name": "InactiveLicensedUsers", "label": "Alert on licensed users that have not logged in for 90 days", + "requiresInput": true, + "inputType": "switch", + "inputLabel": "Exclude disabled users?", + "inputName": "InactiveLicensedUsersExcludeDisabled", "recommendedRunInterval": "1d" }, { "name": "QuotaUsed", "label": "Alert on % mailbox quota used", "requiresInput": true, + "inputType": "textField", "inputLabel": "Enter quota percentage", "inputName": "QuotaUsedQuota", "recommendedRunInterval": "4h" @@ -36,6 +41,7 @@ "name": "SharePointQuota", "label": "Alert on % SharePoint quota used", "requiresInput": true, + "inputType": "textField", "inputLabel": "Enter quota percentage", "inputName": "SharePointQuotaQuota", "recommendedRunInterval": "4h" From dc0ba36772e683e06b089866b2983f0c5fc9ee76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Sat, 18 Jan 2025 11:09:05 +0100 Subject: [PATCH 06/98] fix filter and add comment --- src/pages/tenant/administration/app-consent-requests/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/tenant/administration/app-consent-requests/index.js b/src/pages/tenant/administration/app-consent-requests/index.js index f3237e9345f6..3bc6c53b58d6 100644 --- a/src/pages/tenant/administration/app-consent-requests/index.js +++ b/src/pages/tenant/administration/app-consent-requests/index.js @@ -52,6 +52,7 @@ const Page = () => { return ( setExpanded(!expanded)}> }> @@ -95,7 +96,7 @@ const Page = () => { // Filter for showing only pending requests { filterName: "Pending requests", - value: [{ id: "requestStatus", value: "Pending" }], + value: [{ id: "requestStatus", value: "InProgress" }], type: "column", }, ]} From 723cf72b39708d96f59c30cbad9312eaec0a840f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Sat, 18 Jan 2025 17:40:24 +0100 Subject: [PATCH 07/98] New standard: Add profile photo standard --- src/data/standards.json | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/data/standards.json b/src/data/standards.json index c50f45b20e68..68d06f563a36 100644 --- a/src/data/standards.json +++ b/src/data/standards.json @@ -49,6 +49,36 @@ "powershellEquivalent": "Enable-OrganizationCustomization", "recommendedBy": ["CIS"] }, + { + "name": "standards.ProfilePhotos", + "cat": "Global Standards", + "tag": ["lowimpact"], + "helpText": "Controls whether users can set their own profile photos in Microsoft 365.", + "docsDescription": "Controls whether users can set their own profile photos in Microsoft 365. When disabled, only User and Global administrators can update profile photos for users.", + "addedComponent": [ + { + "type": "select", + "multiple": false, + "label": "Select value", + "name": "standards.ProfilePhotos.state", + "options": [ + { + "label": "Enabled", + "value": "enabled" + }, + { + "label": "Disabled", + "value": "disabled" + } + ] + } + ], + "label": "Allow users to set profile photos", + "impact": "Low Impact", + "impactColour": "info", + "powershellEquivalent": "Set-OrganizationConfig -ProfilePhotoOptions EnablePhotos and Update-MgBetaAdminPeople", + "recommendedBy": [] + }, { "name": "standards.PhishProtection", "cat": "Global Standards", From 53b0b3b798b18768f064fca23c2c5602cfdf9f35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Sat, 18 Jan 2025 18:35:53 +0100 Subject: [PATCH 08/98] Add option to include user mailboxes in shared mailbox sent items delegation --- src/data/standards.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/data/standards.json b/src/data/standards.json index c50f45b20e68..f18aa1972b34 100644 --- a/src/data/standards.json +++ b/src/data/standards.json @@ -1270,7 +1270,13 @@ "tag": ["mediumimpact"], "helpText": "Sets emails sent as and on behalf of shared mailboxes to also be stored in the shared mailbox sent items folder", "docsDescription": "This makes sure that e-mails sent from shared mailboxes or delegate mailboxes, end up in the mailbox of the shared/delegate mailbox instead of the sender, allowing you to keep replies in the same mailbox as the original e-mail.", - "addedComponent": [], + "addedComponent": [ + { + "type": "switch", + "label": "Include user mailboxes", + "name": "standards.DelegateSentItems.IncludeUserMailboxes" + } + ], "label": "Set mailbox Sent Items delegation (Sent items for shared mailboxes)", "impact": "Medium Impact", "impactColour": "warning", From fbe8616121e852e24837463f8ea0bf52139a23b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Sun, 19 Jan 2025 01:52:42 +0100 Subject: [PATCH 09/98] change to POST --- src/pages/email/administration/mailboxes/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/email/administration/mailboxes/index.js b/src/pages/email/administration/mailboxes/index.js index df5d899eb2e2..8ef21abfa990 100644 --- a/src/pages/email/administration/mailboxes/index.js +++ b/src/pages/email/administration/mailboxes/index.js @@ -47,7 +47,7 @@ const Page = () => { }, { label: "Hide from Global Address List", - type: "GET", + type: "POST", url: "/api/ExecHideFromGAL", data: { ID: "UPN", @@ -58,7 +58,7 @@ const Page = () => { }, { label: "Unhide from Global Address List", - type: "GET", + type: "POST", url: "/api/ExecHideFromGAL", data: { ID: "UPN", From d7a218bc8db14b7628a05c4a4630c4ffb8aec001 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Sun, 19 Jan 2025 01:52:54 +0100 Subject: [PATCH 10/98] Add Global Address List report page and menu item --- src/layouts/config.js | 4 + .../reports/global-address-list/index.js | 81 +++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 src/pages/email/reports/global-address-list/index.js diff --git a/src/layouts/config.js b/src/layouts/config.js index 33e0bb77e348..e74843605127 100644 --- a/src/layouts/config.js +++ b/src/layouts/config.js @@ -377,6 +377,10 @@ export const nativeMenuItems = [ title: "Shared Mailbox with Enabled Account", path: "/email/reports/SharedMailboxEnabledAccount", }, + { + title: "Global Address List", + path: "/email/reports/global-address-list", + }, ], }, ], diff --git a/src/pages/email/reports/global-address-list/index.js b/src/pages/email/reports/global-address-list/index.js new file mode 100644 index 000000000000..99087d4cb809 --- /dev/null +++ b/src/pages/email/reports/global-address-list/index.js @@ -0,0 +1,81 @@ +import { Layout as DashboardLayout } from "/src/layouts/index.js"; +import { CippTablePage } from "/src/components/CippComponents/CippTablePage.jsx"; + +const Page = () => { + const actions = [ + { + label: "Unhide from Global Address List", + type: "POST", + url: "/api/ExecHideFromGAL", + data: { + HideFromGAL: false, + ID: "PrimarySmtpAddress", + }, + confirmText: "Are you sure you want to show this mailbox in the Global Address List?", + }, + { + label: "Hide from Global Address List", + type: "POST", + url: "/api/ExecHideFromGAL", + data: { + HideFromGAL: true, + ID: "PrimarySmtpAddress", + }, + confirmText: "Are you sure you want to hide this mailbox from the Global Address List?", + }, + ]; + + const offCanvas = { + extendedInfoFields: [ + "HiddenFromAddressListsEnabled", + "ExternalDirectoryObjectId", + "DisplayName", + "PrimarySmtpAddress", + "RecipientType", + "RecipientTypeDetails", + "IsDirSynced", + "SKUAssigned", + "EmailAddresses", + ], + actions: actions, + }; + + const filters = [ + { + filterName: "Hidden from GAL", + value: [{ id: "HiddenFromAddressListsEnabled", value: "Yes" }], + type: "column", + }, + { + filterName: "Shown in GAL", + value: [{ id: "HiddenFromAddressListsEnabled", value: "No" }], + type: "column", + }, + { + filterName: "Able to be hidden from GAL via CIPP", + value: [{ id: "IsDirSynced", value: "No" }], + type: "column", + }, + ]; + + return ( + + ); +}; + +Page.getLayout = (page) => {page}; + +export default Page; From e4cd6a65650d8331dc760811678d1f301888bf53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Sun, 19 Jan 2025 02:03:10 +0100 Subject: [PATCH 11/98] Update filter name for Global Address List report to 'Cloud only mailboxes' --- src/pages/email/reports/global-address-list/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/email/reports/global-address-list/index.js b/src/pages/email/reports/global-address-list/index.js index 99087d4cb809..5b0be1040072 100644 --- a/src/pages/email/reports/global-address-list/index.js +++ b/src/pages/email/reports/global-address-list/index.js @@ -52,7 +52,7 @@ const Page = () => { type: "column", }, { - filterName: "Able to be hidden from GAL via CIPP", + filterName: "Cloud only mailboxes", value: [{ id: "IsDirSynced", value: "No" }], type: "column", }, From 62230effa8695019fbfae159d4b4c5320e232347 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Sun, 19 Jan 2025 19:18:59 +0100 Subject: [PATCH 12/98] Change device actions from GET to POST and update field names for consistency --- .../identity/administration/devices/index.js | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/src/pages/identity/administration/devices/index.js b/src/pages/identity/administration/devices/index.js index 30cfcc151253..7985b4bcebd9 100644 --- a/src/pages/identity/administration/devices/index.js +++ b/src/pages/identity/administration/devices/index.js @@ -4,46 +4,45 @@ import { Layout as DashboardLayout } from "/src/layouts/index.js"; // had to add const Page = () => { const pageTitle = "Devices"; const actions = [ - // these are currently GET requests that should be converted to POST requests. { label: "Enable Device", - type: "GET", + type: "POST", url: "/api/ExecDeviceDelete", data: { ID: "id", - Action: "!Enable", + action: "Enable", }, confirmText: "Are you sure you want to enable this device?", multiPost: false, }, { label: "Disable Device", - type: "GET", + type: "POST", url: "/api/ExecDeviceDelete", data: { ID: "id", - Action: "!Disable", + action: "Disable", }, confirmText: "Are you sure you want to disable this device?", multiPost: false, }, { - label: "Retrieve Bitlocker Keys", + label: "Retrieve BitLocker Keys", type: "GET", url: "/api/ExecGetRecoveryKey", data: { GUID: "id", }, - confirmText: "Are you sure you want to retrieve the Bitlocker keys?", + confirmText: "Are you sure you want to retrieve the BitLocker keys?", multiPost: false, }, { label: "Delete Device", - type: "GET", + type: "POST", url: "/api/ExecDeviceDelete", data: { ID: "id", - Action: "!Delete", + action: "Delete", }, confirmText: "Are you sure you want to delete this device?", multiPost: false, @@ -51,7 +50,20 @@ const Page = () => { ]; const offCanvas = { - extendedInfoFields: ["createdDateTime", "displayName", "id"], + extendedInfoFields: [ + "accountEnabled", + "displayName", + "id", + "recipientType", + "enrollmentType", + "manufacturer", + "model", + "operatingSystem", + "operatingSystemVersion", + "profileType", + "createdDateTime", + "approximateLastSignInDateTime", + ], actions: actions, }; @@ -77,6 +89,7 @@ const Page = () => { "operatingSystem", "operatingSystemVersion", "profileType", + "approximateLastSignInDateTime", ]} /> ); From 09b44901b0625c2cb2cb6bdf5c6ae55c0921e3eb Mon Sep 17 00:00:00 2001 From: Esco Date: Sun, 19 Jan 2025 19:51:48 +0100 Subject: [PATCH 13/98] feat: added Research Compromised Account to Risky Users --- src/pages/identity/administration/risky-users/index.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/pages/identity/administration/risky-users/index.js b/src/pages/identity/administration/risky-users/index.js index c235ed4e2b2f..b419366b0d83 100644 --- a/src/pages/identity/administration/risky-users/index.js +++ b/src/pages/identity/administration/risky-users/index.js @@ -1,6 +1,7 @@ import { Layout as DashboardLayout } from "/src/layouts/index.js"; import { CippTablePage } from "/src/components/CippComponents/CippTablePage.jsx"; import { Clear } from "@mui/icons-material"; +import { MagnifyingGlassIcon } from "@heroicons/react/24/outline"; const Page = () => { const pageTitle = "Risky Users"; @@ -16,6 +17,14 @@ const Page = () => { confirmText: "Are you sure you want to dismiss the risk for this user?", multiPost: false, }, + { + label: "Research Compromised Account", + type: "GET", + icon: , + link: "/identity/administration/users/user/bec?userId=[id]", + confirmText: "Are you sure you want to research this compromised account?", + multiPost: false, + }, ]; // OffCanvas details based on the source file From d0a89a17715e43ce32a974905814ab9ef2b1adf6 Mon Sep 17 00:00:00 2001 From: Esco Date: Sun, 19 Jan 2025 19:54:56 +0100 Subject: [PATCH 14/98] fix: added MagnifyingGlassIcon to Research Compromised Account --- src/pages/identity/reports/risk-detections/index.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/pages/identity/reports/risk-detections/index.js b/src/pages/identity/reports/risk-detections/index.js index f6e634d0034c..4847fef513db 100644 --- a/src/pages/identity/reports/risk-detections/index.js +++ b/src/pages/identity/reports/risk-detections/index.js @@ -1,5 +1,6 @@ import { Layout as DashboardLayout } from "/src/layouts/index.js"; import { CippTablePage } from "/src/components/CippComponents/CippTablePage.jsx"; +import { MagnifyingGlassIcon } from "@heroicons/react/24/outline"; const Page = () => { const pageTitle = "Risk Detection Report"; @@ -8,8 +9,11 @@ const Page = () => { const actions = [ { label: "Research Compromised Account", - link: "/identity/administration/users/user/bec?userId=[userId]&tenantFilter=[Tenant]", - color: "info", + type: "GET", + icon: , + link: "/identity/administration/users/user/bec?userId=[userId]", + confirmText: "Are you sure you want to research this compromised account?", + multiPost: false, }, ]; From 519a67f55f32a4b62e81731f21c6b284f715d8d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Sun, 19 Jan 2025 20:48:01 +0100 Subject: [PATCH 15/98] Add standard for cleaning up stale Entra devices with configurable age threshold --- src/data/standards.json | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/data/standards.json b/src/data/standards.json index c50f45b20e68..864eb6fd3ad5 100644 --- a/src/data/standards.json +++ b/src/data/standards.json @@ -644,6 +644,30 @@ "powershellEquivalent": "", "recommendedBy": [] }, + { + "name": "standards.StaleEntraDevices", + "cat": "Entra (AAD) Standards", + "tag": ["highimpact", "CIS"], + "helpText": "Cleans up Entra devices that have not connected/signed in for the specified number of days.", + "docsDescription": "Cleans up Entra devices that have not connected/signed in for the specified number of days. First disables and later deletes the devices. More info can be found in the [Microsoft documentation](https://learn.microsoft.com/en-us/entra/identity/devices/manage-stale-devices)", + "addedComponent": [ + { + "type": "number", + "name": "standards.StaleEntraDevices.deviceAgeThreshold", + "label": "Days before stale(Dont set below 30)" + } + ], + "disabledFeatures": { + "report": false, + "warn": false, + "remediate": true + }, + "label": "Cleanup stale Entra devices", + "impact": "High Impact", + "impactColour": "danger", + "powershellEquivalent": "Remove-MgDevice, Update-MgDevice or Graph API", + "recommendedBy": [] + }, { "name": "standards.UndoOauth", "cat": "Entra (AAD) Standards", From e7deb88791df7392412f59c03aa61c36e0f90feb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Sun, 19 Jan 2025 21:27:38 +0100 Subject: [PATCH 16/98] Update device actions and remove unused offCanvas configuration --- .../identity/administration/devices/index.js | 25 +++---------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/src/pages/identity/administration/devices/index.js b/src/pages/identity/administration/devices/index.js index 7985b4bcebd9..d58914fdcbec 100644 --- a/src/pages/identity/administration/devices/index.js +++ b/src/pages/identity/administration/devices/index.js @@ -10,7 +10,7 @@ const Page = () => { url: "/api/ExecDeviceDelete", data: { ID: "id", - action: "Enable", + action: "!Enable", }, confirmText: "Are you sure you want to enable this device?", multiPost: false, @@ -21,7 +21,7 @@ const Page = () => { url: "/api/ExecDeviceDelete", data: { ID: "id", - action: "Disable", + action: "!Disable", }, confirmText: "Are you sure you want to disable this device?", multiPost: false, @@ -42,31 +42,13 @@ const Page = () => { url: "/api/ExecDeviceDelete", data: { ID: "id", - action: "Delete", + action: "!Delete", }, confirmText: "Are you sure you want to delete this device?", multiPost: false, }, ]; - const offCanvas = { - extendedInfoFields: [ - "accountEnabled", - "displayName", - "id", - "recipientType", - "enrollmentType", - "manufacturer", - "model", - "operatingSystem", - "operatingSystemVersion", - "profileType", - "createdDateTime", - "approximateLastSignInDateTime", - ], - actions: actions, - }; - return ( { }} apiDataKey="Results" actions={actions} - offCanvas={offCanvas} simpleColumns={[ "displayName", "accountEnabled", From a9ca690681c36b70b9ae214cbdd75f22db343033 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Sun, 19 Jan 2025 22:06:29 +0100 Subject: [PATCH 17/98] quota stuff --- .../CippComponents/CippTranslations.jsx | 2 ++ src/utils/get-cipp-formatting.js | 22 +++++++++++++------ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/components/CippComponents/CippTranslations.jsx b/src/components/CippComponents/CippTranslations.jsx index dade5cfd0d6b..0876bded5bd2 100644 --- a/src/components/CippComponents/CippTranslations.jsx +++ b/src/components/CippComponents/CippTranslations.jsx @@ -42,4 +42,6 @@ export const CippTranslations = { ToIP: "To IP", "info.logoUrl": "Logo", "commitmentTerm.renewalConfiguration.renewalDate": "Renewal Date", + storageUsedInBytes: "Storage Used", + prohibitSendReceiveQuotaInBytes: "Quota", }; diff --git a/src/utils/get-cipp-formatting.js b/src/utils/get-cipp-formatting.js index 6bac0ef28080..a1dbf12c0349 100644 --- a/src/utils/get-cipp-formatting.js +++ b/src/utils/get-cipp-formatting.js @@ -59,6 +59,20 @@ export const getCippFormatting = (data, cellName, type, canReceive) => { ); } + if (cellName === "prohibitSendReceiveQuotaInBytes" || cellName === "storageUsedInBytes") { + //convert bytes to GB + const bytes = data; + if (bytes === null || bytes === undefined) { + return isText ? ( + "No data" + ) : ( + + ); + } + const gb = bytes / 1024 / 1024 / 1024; + return isText ? `${gb.toFixed(2)} GB` : `${gb.toFixed(2)} GB`; + } + if (cellName === "info.logoUrl") { return isText ? ( data @@ -337,13 +351,7 @@ export const getCippFormatting = (data, cellName, type, canReceive) => { ); } - const translateProps = [ - "riskLevel", - "riskState", - "riskDetail", - "enrollmentType", - "profileType", - ]; + const translateProps = ["riskLevel", "riskState", "riskDetail", "enrollmentType", "profileType"]; if (translateProps.includes(cellName)) { return getCippTranslation(data); From 8cbe7ba8de8d696c15bfdb28745ac4cf24c280e6 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Sun, 19 Jan 2025 23:19:28 +0100 Subject: [PATCH 18/98] reintroduce central search --- .../CippComponents/CippCentralSearch.jsx | 140 ++++++++++++++++++ src/layouts/top-nav.js | 29 +++- 2 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 src/components/CippComponents/CippCentralSearch.jsx diff --git a/src/components/CippComponents/CippCentralSearch.jsx b/src/components/CippComponents/CippCentralSearch.jsx new file mode 100644 index 000000000000..c710a8d2d49b --- /dev/null +++ b/src/components/CippComponents/CippCentralSearch.jsx @@ -0,0 +1,140 @@ +import React, { useState } from "react"; +import { + Button, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + TextField, + Grid, + Card, + CardContent, + Typography, + Box, +} from "@mui/material"; +import { useRouter } from "next/router"; +import { nativeMenuItems } from "/src/layouts/config"; + +/** + * Recursively collects only leaf items (those without sub-items). + * If an item has an `items` array, we skip that item itself (treat it as a "folder") + * and continue flattening deeper. + */ +function getLeafItems(items = []) { + let result = []; + + for (const item of items) { + if (item.items && Array.isArray(item.items) && item.items.length > 0) { + // recurse into children + result = result.concat(getLeafItems(item.items)); + } else { + // no child items => this is a leaf + result.push(item); + } + } + + return result; +} + +export const CippCentralSearch = ({ handleClose, open }) => { + const router = useRouter(); + const [searchValue, setSearchValue] = useState(""); + + // Flatten the menu items once + const flattenedMenuItems = getLeafItems(nativeMenuItems); + + const handleChange = (event) => { + setSearchValue(event.target.value); + }; + + // Optionally handle Enter key + const handleKeyDown = (event) => { + if (event.key === "Enter") { + // do something if needed, e.g., analytics or highlight + } + }; + + // Filter leaf items by matching title or path + const normalizedSearch = searchValue.trim().toLowerCase(); + const filteredItems = flattenedMenuItems.filter((leaf) => { + const inTitle = leaf.title?.toLowerCase().includes(normalizedSearch); + const inPath = leaf.path?.toLowerCase().includes(normalizedSearch); + // If there's no search value, show no results (you could change this logic) + return normalizedSearch ? inTitle || inPath : false; + }); + + // Helper to bold‐highlight the matched text + const highlightMatch = (text = "") => { + if (!normalizedSearch) return text; + const parts = text.split(new RegExp(`(${normalizedSearch})`, "gi")); + return parts.map((part, i) => + part.toLowerCase() === normalizedSearch ? ( + + {part} + + ) : ( + part + ) + ); + }; + + // Click handler: shallow navigate with Next.js + const handleCardClick = (path) => { + router.push(path, undefined, { shallow: true }); + handleClose(); + }; + + return ( + + CIPP Search + + + + + + {/* Show results or "No results" */} + {searchValue.trim().length > 0 ? ( + filteredItems.length > 0 ? ( + + {filteredItems.map((item, index) => ( + + handleCardClick(item.path)} + > + + {highlightMatch(item.title)} + + Path: {highlightMatch(item.path)} + + + + + ))} + + ) : ( + No results found. + ) + ) : ( + + Type something to search by title or path. + + )} + + + + + + + ); +}; diff --git a/src/layouts/top-nav.js b/src/layouts/top-nav.js index cc229bcd9daf..b92b1ace3d31 100644 --- a/src/layouts/top-nav.js +++ b/src/layouts/top-nav.js @@ -1,4 +1,4 @@ -import { useCallback } from "react"; +import { useCallback, useEffect } from "react"; import NextLink from "next/link"; import PropTypes from "prop-types"; import Bars3Icon from "@heroicons/react/24/outline/Bars3Icon"; @@ -11,9 +11,13 @@ import { paths } from "../paths"; import { AccountPopover } from "./account-popover"; import { CippTenantSelector } from "../components/CippComponents/CippTenantSelector"; import { NotificationsPopover } from "./notifications-popover"; +import { useDialog } from "../hooks/use-dialog"; +import { MagnifyingGlassCircleIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outline"; +import { CippCentralSearch } from "../components/CippComponents/CippCentralSearch"; const TOP_NAV_HEIGHT = 64; export const TopNav = (props) => { + const searchDialog = useDialog(); const { onNavOpen } = props; const settings = useSettings(); const mdDown = useMediaQuery((theme) => theme.breakpoints.down("md")); @@ -24,6 +28,23 @@ export const TopNav = (props) => { }); }, [settings]); + useEffect(() => { + const handleKeyDown = (event) => { + if ((event.metaKey || event.ctrlKey) && event.key === "k") { + event.preventDefault(); + openSearch(); + } + }; + window.addEventListener("keydown", handleKeyDown); + return () => { + window.removeEventListener("keydown", handleKeyDown); + }; + }, []); + + const openSearch = () => { + searchDialog.handleOpen(); + }; + return ( { )} + openSearch()}> + + + + + Date: Mon, 20 Jan 2025 00:55:27 +0100 Subject: [PATCH 19/98] improved fuzzy search --- src/components/CippComponents/CippAutocomplete.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/CippComponents/CippAutocomplete.jsx b/src/components/CippComponents/CippAutocomplete.jsx index 1513e4ca8da1..8cef03878f94 100644 --- a/src/components/CippComponents/CippAutocomplete.jsx +++ b/src/components/CippComponents/CippAutocomplete.jsx @@ -25,7 +25,7 @@ export const CippAutoComplete = (props) => { sx, ...other } = props; - const filter = createFilterOptions(); + const filter = createFilterOptions({ stringify: (option) => JSON.stringify(option) }); const [usedOptions, setUsedOptions] = useState(options); const [getRequestInfo, setGetRequestInfo] = useState({ url: "", waiting: false, queryKey: "" }); From a11d508264e557d21cdb18dd9073bd4b2140a8c2 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Mon, 20 Jan 2025 01:26:31 +0100 Subject: [PATCH 20/98] added resubmit text --- src/components/CippComponents/CippApiDialog.jsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/components/CippComponents/CippApiDialog.jsx b/src/components/CippComponents/CippApiDialog.jsx index c724f0ddac05..f2987083d627 100644 --- a/src/components/CippComponents/CippApiDialog.jsx +++ b/src/components/CippComponents/CippApiDialog.jsx @@ -58,8 +58,6 @@ export const CippApiDialog = (props) => { return api.dataFunction(row); } var newData = {}; - console.log("the received row", row); - console.log("the received dataObject", dataObject); if (api?.postEntireRow) { newData = row; @@ -87,7 +85,6 @@ export const CippApiDialog = (props) => { } }); } - console.log("output", newData); return newData; }; const tenantFilter = useSettings().currentTenant; @@ -254,7 +251,8 @@ export const CippApiDialog = (props) => { Close From 9d1e3176fca2b77edd86b8720b373af60f7d94d5 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Mon, 20 Jan 2025 01:27:21 +0100 Subject: [PATCH 21/98] remove console logs --- src/components/CippComponents/CippApiDialog.jsx | 1 - src/components/CippTable/CIPPTableToptoolbar.js | 1 - src/pages/index.js | 1 - src/pages/tools/breachlookup/index.js | 1 - 4 files changed, 4 deletions(-) diff --git a/src/components/CippComponents/CippApiDialog.jsx b/src/components/CippComponents/CippApiDialog.jsx index f2987083d627..84bf69a17599 100644 --- a/src/components/CippComponents/CippApiDialog.jsx +++ b/src/components/CippComponents/CippApiDialog.jsx @@ -251,7 +251,6 @@ export const CippApiDialog = (props) => { Close diff --git a/src/components/CippTable/CIPPTableToptoolbar.js b/src/components/CippTable/CIPPTableToptoolbar.js index 92ee885a364c..547be5c02718 100644 --- a/src/components/CippTable/CIPPTableToptoolbar.js +++ b/src/components/CippTable/CIPPTableToptoolbar.js @@ -267,7 +267,6 @@ export const CIPPTableToptoolbar = ({ } else if (data && !getRequestData.isFetched) { //do nothing because data was sent native. } else if (getRequestData) { - console.log(getRequestData); getRequestData.refetch(); } }} diff --git a/src/pages/index.js b/src/pages/index.js index b8ffad358dd8..f7de4e0e5370 100644 --- a/src/pages/index.js +++ b/src/pages/index.js @@ -135,7 +135,6 @@ const Page = () => { if (!Array.isArray(actions)) { actions = [actions]; } - console.log("actions is", actions); actions.forEach((actionObj) => { if (actionObj?.value === "Remediate") { remediateCount++; diff --git a/src/pages/tools/breachlookup/index.js b/src/pages/tools/breachlookup/index.js index 344ef279d790..b7da246bd174 100644 --- a/src/pages/tools/breachlookup/index.js +++ b/src/pages/tools/breachlookup/index.js @@ -234,7 +234,6 @@ const Page = () => { )} - {console.log(getGeoIP.error)} From e7376c637ee6a805ea1a3180a4768c57c8464e84 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Mon, 20 Jan 2025 10:51:20 +0100 Subject: [PATCH 22/98] fixed issue with hanging page --- .../administration/users/user/conditional-access.jsx | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/pages/identity/administration/users/user/conditional-access.jsx b/src/pages/identity/administration/users/user/conditional-access.jsx index bb42a2a87f03..6bda3f8534c2 100644 --- a/src/pages/identity/administration/users/user/conditional-access.jsx +++ b/src/pages/identity/administration/users/user/conditional-access.jsx @@ -25,12 +25,8 @@ const Page = () => { const { userId } = router.query; const tenant = userSettingsDefaults.currentTenant; - const currentSettings = userSettingsDefaults.currentSettings; // Assuming currentSettings is part of useSettings + const [formParams, setFormParams] = useState(false); - // State for form parameters - const [formParams, setFormParams] = useState(null); - - // Fetch user details for the header const userRequest = ApiGetCall({ url: `/api/ListUsers?UserId=${userId}&tenantFilter=${tenant}`, queryKey: `ListUsers-${userId}`, @@ -101,7 +97,6 @@ const Page = () => { Test policies } - cardLabelBox={currentSettings?.ForwardAndDeliver ? : "-"} // Optional: Display an icon or placeholder > {/* Form Starts Here */}
@@ -234,7 +229,7 @@ const Page = () => { title={"CA Test Results"} simple={true} simpleColumns={["displayName", "state", "policyApplies", "reasons"]} - data={postRequest.data?.data?.Results?.value} + data={postRequest.data?.data?.Results?.value || []} /> From 1bab8f8b84a18c658dc78efc8821620bc9f13186 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Mon, 20 Jan 2025 13:22:39 +0100 Subject: [PATCH 23/98] bug fix empty results --- src/pages/tenant/administration/graph-explorer/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/tenant/administration/graph-explorer/index.js b/src/pages/tenant/administration/graph-explorer/index.js index 2e984b4c9bdd..05d03a30b75b 100644 --- a/src/pages/tenant/administration/graph-explorer/index.js +++ b/src/pages/tenant/administration/graph-explorer/index.js @@ -22,7 +22,7 @@ const Page = () => { } title={pageTitle} apiDataKey="Results" - apiUrl={apiFilter.endpoint ? "/api/ListGraphRequest" : null} + apiUrl={apiFilter.endpoint ? "/api/ListGraphRequest" : "/api/ListEmptyResults"} apiData={apiFilter} queryKey={queryKey} /*Key={`${apiFilter.endpoint}-${apiFilter.$select}`}*/ From a03c000dc921b50dfdcebd186db07ae0246e861e Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 20 Jan 2025 11:25:39 -0500 Subject: [PATCH 24/98] add extra help for password push --- src/data/Extensions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/Extensions.json b/src/data/Extensions.json index 7bdb55f719d2..d9bce72744ba 100644 --- a/src/data/Extensions.json +++ b/src/data/Extensions.json @@ -334,7 +334,7 @@ "logoDark": "/assets/integrations/pwpush_dark.png", "forceSyncButton": false, "description": "Enable the PasswordPusher integration to generate password links instead of plain text passwords.", - "helpText": "This integration allows you to generate password links instead of plain text passwords. Configure authentication and expiration settings that will apply to all generated passwords.", + "helpText": "This integration allows you to generate password links instead of plain text passwords. Configure authentication and expiration settings that will apply to all generated passwords. If you are a PWPush Pro customer and utilizing custom domains, please do not enter your custom domain in the Base URL field.", "links": [ { "name": "PWPush Documentation", From aa136b526a28ccf29383f06509f994095a8a959c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Mon, 20 Jan 2025 17:39:25 +0100 Subject: [PATCH 25/98] FIX actions bug and add filters --- src/pages/email/transport/list-rules/index.js | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/pages/email/transport/list-rules/index.js b/src/pages/email/transport/list-rules/index.js index d7855ebe5bb1..448c609e9265 100644 --- a/src/pages/email/transport/list-rules/index.js +++ b/src/pages/email/transport/list-rules/index.js @@ -19,8 +19,7 @@ const Page = () => { type: "POST", url: "/api/EditTransportRule", data: { - State: "Enable", - TenantFilter: "Tenant", + State: "!Enable", GUID: "Guid", }, confirmText: "Are you sure you want to enable this rule?", @@ -30,8 +29,7 @@ const Page = () => { type: "POST", url: "/api/EditTransportRule", data: { - State: "Disable", - TenantFilter: "Tenant", + State: "!Disable", GUID: "Guid", }, confirmText: "Are you sure you want to disable this rule?", @@ -41,7 +39,6 @@ const Page = () => { type: "POST", url: "/api/RemoveTransportRule", data: { - TenantFilter: "Tenant", GUID: "Guid", }, confirmText: "Are you sure you want to delete this rule?", @@ -50,11 +47,11 @@ const Page = () => { ]; const offCanvas = { - extendedInfoFields: ["CreatedBy", "LastModifiedBy", "Description"], + extendedInfoFields: ["CreatedBy", "LastModifiedBy", "WhenChanged", "Description", "Guid"], actions: actions, }; - const simpleColumns = ["Name", "State", "Mode", "RuleErrorAction"]; + const simpleColumns = ["Name", "State", "Mode", "RuleErrorAction", "WhenChanged"]; return ( { actions={actions} offCanvas={offCanvas} simpleColumns={simpleColumns} + filters={[ + { + filterName: "Enabled Rules", + value: [{ id: "State", value: "Enabled" }], + type: "column", + }, + { + filterName: "Disabled Rules", + value: [{ id: "State", value: "Disabled" }], + type: "column", + }, + ]} cardButton={ <> + ( + + )} + /> + ( + } + label="Enable HTTPS check" + /> + )} + /> + {enableHttps && ( + ( + + )} + /> + )} + + @@ -605,6 +683,25 @@ export const CippDomainCards = ({ domain: propDomain = "", fullwidth = false }) } /> + {enableHttps && ( + + + + + } + /> + + )} )} diff --git a/src/utils/get-cipp-formatting.js b/src/utils/get-cipp-formatting.js index a1dbf12c0349..916c9d461dc3 100644 --- a/src/utils/get-cipp-formatting.js +++ b/src/utils/get-cipp-formatting.js @@ -108,6 +108,8 @@ export const getCippFormatting = (data, cellName, type, canReceive) => { "purchaseDate", "NextOccurrence", "LastOccurrence", + "NotBefore", + "NotAfter", ]; const matchDateTime = /[dD]ate[tT]ime/; From 972bd9de3e5712c315a440af3e5356e53781c412 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Wed, 22 Jan 2025 22:10:18 +0100 Subject: [PATCH 50/98] fixes --- src/pages/endpoint/applications/list/add.jsx | 30 +++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/pages/endpoint/applications/list/add.jsx b/src/pages/endpoint/applications/list/add.jsx index ca226d78cc26..7bceb2e69857 100644 --- a/src/pages/endpoint/applications/list/add.jsx +++ b/src/pages/endpoint/applications/list/add.jsx @@ -86,6 +86,7 @@ const ApplicationDeploymentForm = () => { const formattedData = { ...data }; formattedData.selectedTenants = selectedTenants.map((tenant) => ({ defaultDomainName: tenant.value, + customerId: tenant.addedFields.customerId, })); return formattedData; }} @@ -174,11 +175,12 @@ const ApplicationDeploymentForm = () => { /> {selectedTenants?.map((tenant, index) => ( - + + {console.log(tenant)} @@ -194,11 +196,11 @@ const ApplicationDeploymentForm = () => { compareValue="syncro" > {selectedTenants?.map((tenant, index) => ( - + @@ -215,11 +217,11 @@ const ApplicationDeploymentForm = () => { compareValue="immy" > {selectedTenants?.map((tenant, index) => ( - + @@ -244,11 +246,11 @@ const ApplicationDeploymentForm = () => { /> {selectedTenants?.map((tenant, index) => ( - + @@ -273,22 +275,22 @@ const ApplicationDeploymentForm = () => { /> {selectedTenants?.map((tenant, index) => ( - + ))} {selectedTenants?.map((tenant, index) => ( - + @@ -304,11 +306,11 @@ const ApplicationDeploymentForm = () => { compareValue="cwcommand" > {selectedTenants?.map((tenant, index) => ( - + From 455f49d43fc2f26a022a05c5016ae879fd474c80 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Wed, 22 Jan 2025 22:59:00 +0100 Subject: [PATCH 51/98] bring back see more --- src/pages/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/index.js b/src/pages/index.js index f7de4e0e5370..27edd71fbb1e 100644 --- a/src/pages/index.js +++ b/src/pages/index.js @@ -259,7 +259,7 @@ const Page = () => { label: "", value: domain.name, }))} - actionItems={ + cardButton={ organization.data?.verifiedDomains?.length > 3 && ( } - apiUrl="/api/ListGroups" + apiUrl="/api/ListGraphRequest" + apiData={{ + Endpoint: "groups", + $select: + "id,createdDateTime,displayName,description,mail,mailEnabled,mailNickname,resourceProvisioningOptions,securityEnabled,visibility,organizationId,onPremisesSamAccountName,membershipRule,grouptypes,onPremisesSyncEnabled,resourceProvisioningOptions,userPrincipalName,assignedLicenses", + $count: true, + $orderby: "displayName", + $top: 999, + manualPagination: true, + }} + apiDataKey="Results" actions={actions} offCanvas={offCanvas} simpleColumns={[ @@ -115,6 +131,7 @@ const Page = () => { "mailEnabled", "mailNickname", "calculatedGroupType", + "assignedLicenses", "visibility", "onPremisesSamAccountName", "membershipRule", From 273cddefd79d147272e05a6e473ae12d203434e0 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 22 Jan 2025 22:05:55 -0500 Subject: [PATCH 57/98] view user improvements add CippUserActions component add hideTitle param to CippDataTable add table option to CippBannerListCard add group/role membership banner cards to view user --- .../CippCards/CippBannerListCard.jsx | 38 +- .../CippComponents/CippUserActions.jsx | 317 ++++++++++++++++ src/components/CippTable/CippDataTable.js | 9 +- .../identity/administration/users/index.js | 316 +--------------- .../administration/users/user/index.jsx | 356 ++++-------------- 5 files changed, 414 insertions(+), 622 deletions(-) create mode 100644 src/components/CippComponents/CippUserActions.jsx diff --git a/src/components/CippCards/CippBannerListCard.jsx b/src/components/CippCards/CippBannerListCard.jsx index 41d3609a1756..a0bab7612a02 100644 --- a/src/components/CippCards/CippBannerListCard.jsx +++ b/src/components/CippCards/CippBannerListCard.jsx @@ -13,6 +13,7 @@ import { } from "@mui/material"; import ChevronDownIcon from "@heroicons/react/24/outline/ChevronDownIcon"; import { CippPropertyListCard } from "./CippPropertyListCard"; +import { CippDataTable } from "../CippTable/CippDataTable"; export const CippBannerListCard = (props) => { const { items = [], isCollapsible = false, isFetching = false, ...other } = props; @@ -113,17 +114,19 @@ export const CippBannerListCard = (props) => { {/* Right Side: Status and Expand Icon */} - - - {item.statusText} - + {item?.statusText && ( + + + {item.statusText} + + )} {isCollapsible && ( handleExpand(item.id)}> { {isCollapsible && ( - + {item?.propertyItems?.length > 0 && ( + + )} + {item?.table && } )} diff --git a/src/components/CippComponents/CippUserActions.jsx b/src/components/CippComponents/CippUserActions.jsx new file mode 100644 index 000000000000..fe532d259f4b --- /dev/null +++ b/src/components/CippComponents/CippUserActions.jsx @@ -0,0 +1,317 @@ +import { EyeIcon, MagnifyingGlassIcon, TrashIcon } from "@heroicons/react/24/outline"; +import { + Archive, + Block, + Clear, + CloudDone, + Edit, + Email, + ForwardToInbox, + GroupAdd, + LockOpen, + LockPerson, + LockReset, + MeetingRoom, + NoMeetingRoom, + Password, + PersonOff, + PhonelinkLock, + PhonelinkSetup, + Shortcut, +} from "@mui/icons-material"; +import { useSettings } from "/src/hooks/use-settings.js"; + +export const CippUserActions = () => { + const tenant = useSettings().currentTenant; + return [ + { + //tested + label: "View User", + link: "/identity/administration/users/user?userId=[id]", + multiPost: false, + icon: , + color: "success", + }, + { + //tested + label: "Edit User", + link: "/identity/administration/users/user/edit?userId=[id]", + icon: , + color: "success", + target: "_self", + }, + { + //tested + label: "Research Compromised Account", + type: "GET", + icon: , + link: "/identity/administration/users/user/bec?userId=[id]", + confirmText: "Are you sure you want to research this compromised account?", + multiPost: false, + }, + { + //tested + + label: "Create Temporary Access Password", + type: "GET", + icon: , + url: "/api/ExecCreateTAP", + data: { ID: "userPrincipalName" }, + confirmText: "Are you sure you want to create a Temporary Access Password?", + multiPost: false, + }, + { + //tested + label: "Re-require MFA registration", + type: "GET", + icon: , + url: "/api/ExecResetMFA", + data: { ID: "userPrincipalName" }, + confirmText: "Are you sure you want to reset MFA for this user?", + multiPost: false, + }, + { + //tested + label: "Send MFA Push", + type: "POST", + icon: , + url: "/api/ExecSendPush", + data: { UserEmail: "userPrincipalName" }, + confirmText: "Are you sure you want to send an MFA request?", + multiPost: false, + }, + { + //tested + label: "Set Per-User MFA", + type: "POST", + icon: , + url: "/api/ExecPerUserMFA", + data: { userId: "userPrincipalName" }, + fields: [ + { + type: "autoComplete", + name: "State", + label: "State", + options: [ + { label: "Enforced", value: "Enforced" }, + { label: "Enabled", value: "Enabled" }, + { label: "Disabled", value: "Disabled" }, + ], + multiple: false, + }, + ], + confirmText: "Are you sure you want to set per-user MFA for these users?", + multiPost: false, + }, + { + //tested + label: "Convert to Shared Mailbox", + type: "GET", + icon: , + url: "/api/ExecConvertToSharedMailbox", + data: { ID: "userPrincipalName" }, + confirmText: "Are you sure you want to convert this user to a shared mailbox?", + multiPost: false, + }, + { + label: "Convert to User Mailbox", + type: "GET", + icon: , + url: "/api/ExecConvertToSharedMailbox", + data: { ID: "userPrincipalName", ConvertToUser: true }, + confirmText: "Are you sure you want to convert this user to a user mailbox?", + multiPost: false, + }, + { + //tested + label: "Enable Online Archive", + type: "GET", + icon: , + url: "/api/ExecEnableArchive", + data: { ID: "userPrincipalName" }, + confirmText: "Are you sure you want to enable the online archive for this user?", + multiPost: false, + }, + { + //tested + label: "Set Out of Office", + type: "POST", + icon: , + url: "/api/ExecSetOoO", + data: { + userId: "userPrincipalName", + AutoReplyState: { value: "Enabled" }, + tenantFilter: "Tenant", + }, + fields: [{ type: "richText", name: "input", label: "Out of Office Message" }], + confirmText: "Are you sure you want to set the out of office?", + multiPost: false, + }, + + { + label: "Disable Out of Office", + type: "POST", + icon: , + url: "/api/ExecSetOoO", + data: { user: "userPrincipalName", AutoReplyState: "Disabled" }, + confirmText: "Are you sure you want to disable the out of office?", + multiPost: false, + }, + { + label: "Add to Group", + type: "POST", + icon: , + url: "/api/EditGroup", + data: { addMember: "userPrincipalName" }, + fields: [ + { + type: "autoComplete", + name: "groupId", + label: "Select a group to add the user to", + multiple: false, + creatable: false, + api: { + url: "/api/ListGroups", + labelField: "displayName", + valueField: "id", + addedField: { + groupType: "calculatedGroupType", + groupName: "displayName", + }, + queryKey: `groups-${tenant}`, + }, + }, + ], + confirmText: "Are you sure you want to add the user to this group?", + }, + { + label: "Disable Email Forwarding", + type: "POST", + url: "/api/ExecEmailForward", + icon: , + data: { + username: "userPrincipalName", + userid: "userPrincipalName", + ForwardOption: "!disabled", + }, + confirmText: "Are you sure you want to disable forwarding of this user's emails?", + multiPost: false, + }, + { + label: "Pre-provision OneDrive", + type: "POST", + icon: , + url: "/api/ExecOneDriveProvision", + data: { UserPrincipalName: "userPrincipalName" }, + confirmText: "Are you sure you want to pre-provision OneDrive for this user?", + multiPost: false, + }, + { + label: "Add OneDrive Shortcut", + type: "POST", + icon: , + url: "/api/ExecOneDriveShortCut", + data: { + username: "userPrincipalName", + userid: "id", + }, + fields: [ + { + type: "autoComplete", + name: "siteUrl", + label: "Select a Site", + multiple: false, + creatable: false, + api: { + url: "/api/ListSites", + data: { type: "SharePointSiteUsage", URLOnly: true }, + labelField: "webUrl", + valueField: "webUrl", + queryKey: `sharepointSites-${tenant}`, + }, + }, + ], + confirmText: "Select a SharePoint site to create a shortcut for:", + multiPost: false, + }, + { + label: "Block Sign In", + type: "GET", + icon: , + url: "/api/ExecDisableUser", + data: { ID: "id" }, + confirmText: "Are you sure you want to block the sign-in for this user?", + multiPost: false, + condition: (row) => row.accountEnabled, + }, + { + label: "Unblock Sign In", + type: "GET", + icon: , + url: "/api/ExecDisableUser", + data: { ID: "id", Enable: true }, + confirmText: "Are you sure you want to unblock sign-in for this user?", + multiPost: false, + condition: (row) => !row.accountEnabled, + }, + { + label: "Reset Password (Must Change)", + type: "GET", + icon: , + url: "/api/ExecResetPass", + data: { + MustChange: true, + ID: "userPrincipalName", + displayName: "displayName", + }, + confirmText: + "Are you sure you want to reset the password for this user? The user must change their password at next logon.", + multiPost: false, + }, + { + label: "Reset Password", + type: "GET", + icon: , + url: "/api/ExecResetPass", + data: { + MustChange: false, + ID: "userPrincipalName", + displayName: "displayName", + }, + confirmText: "Are you sure you want to reset the password for this user?", + multiPost: false, + }, + { + label: "Clear Immutable ID", + type: "GET", + icon: , + url: "/api/ExecClrImmId", + data: { + ID: "id", + }, + confirmText: "Are you sure you want to clear the Immutable ID for this user?", + multiPost: false, + condition: (row) => row.onPremisesSyncEnabled, + }, + { + label: "Revoke all user sessions", + type: "GET", + icon: , + url: "/api/ExecRevokeSessions", + data: { ID: "id", Username: "userPrincipalName" }, + confirmText: "Are you sure you want to revoke all sessions for this user?", + multiPost: false, + }, + { + label: "Delete User", + type: "GET", + icon: , + url: "/api/RemoveUser", + data: { ID: "id" }, + confirmText: "Are you sure you want to delete this user?", + multiPost: false, + }, + ]; +}; + +export default CippUserActions; diff --git a/src/components/CippTable/CippDataTable.js b/src/components/CippTable/CippDataTable.js index 0eb40ee4d575..6d59e6f372d6 100644 --- a/src/components/CippTable/CippDataTable.js +++ b/src/components/CippTable/CippDataTable.js @@ -46,6 +46,7 @@ export const CippDataTable = (props) => { cardButton, offCanvas = false, noCard = false, + hideTitle = false, refreshFunction, incorrectDataMessage = "Data not in correct format", onChange, @@ -298,8 +299,12 @@ export const CippDataTable = (props) => { ) : ( // Render the table inside a Card - - + {cardButton || !hideTitle ? ( + <> + + + + ) : null} {!Array.isArray(usedData) && usedData ? ( diff --git a/src/pages/identity/administration/users/index.js b/src/pages/identity/administration/users/index.js index aff8bcf7792a..c03a62bb56cd 100644 --- a/src/pages/identity/administration/users/index.js +++ b/src/pages/identity/administration/users/index.js @@ -1,324 +1,14 @@ -import { EyeIcon, MagnifyingGlassIcon, TrashIcon } from "@heroicons/react/24/outline"; import { CippTablePage } from "/src/components/CippComponents/CippTablePage.jsx"; import { Layout as DashboardLayout } from "/src/layouts/index.js"; -import { - Archive, - Block, - Clear, - CloudDone, - Edit, - Email, - ForwardToInbox, - GroupAdd, - LockOpen, - LockPerson, - LockReset, - MeetingRoom, - NoMeetingRoom, - Password, - PersonOff, - PhonelinkLock, - PhonelinkSetup, - Shortcut, -} from "@mui/icons-material"; import { Button } from "@mui/material"; import Link from "next/link"; import { useSettings } from "/src/hooks/use-settings.js"; +import { CippUserActions } from "/src/components/CippComponents/CippUserActions.jsx"; const Page = () => { const pageTitle = "Users"; const tenant = useSettings().currentTenant; - const actions = [ - { - //tested - label: "View User", - link: "/identity/administration/users/user?userId=[id]", - multiPost: false, - icon: , - color: "success", - }, - { - //tested - label: "Edit User", - link: "/identity/administration/users/user/edit?userId=[id]", - icon: , - color: "success", - target: "_self", - }, - { - //tested - label: "Research Compromised Account", - type: "GET", - icon: , - link: "/identity/administration/users/user/bec?userId=[id]", - confirmText: "Are you sure you want to research this compromised account?", - multiPost: false, - }, - { - //tested - - label: "Create Temporary Access Password", - type: "GET", - icon: , - url: "/api/ExecCreateTAP", - data: { ID: "userPrincipalName" }, - confirmText: "Are you sure you want to create a Temporary Access Password?", - multiPost: false, - }, - { - //tested - label: "Re-require MFA registration", - type: "GET", - icon: , - url: "/api/ExecResetMFA", - data: { ID: "userPrincipalName" }, - confirmText: "Are you sure you want to reset MFA for this user?", - multiPost: false, - }, - { - //tested - label: "Send MFA Push", - type: "POST", - icon: , - url: "/api/ExecSendPush", - data: { UserEmail: "userPrincipalName" }, - confirmText: "Are you sure you want to send an MFA request?", - multiPost: false, - }, - { - //tested - label: "Set Per-User MFA", - type: "POST", - icon: , - url: "/api/ExecPerUserMFA", - data: { userId: "userPrincipalName" }, - fields: [ - { - type: "autoComplete", - name: "State", - label: "State", - options: [ - { label: "Enforced", value: "Enforced" }, - { label: "Enabled", value: "Enabled" }, - { label: "Disabled", value: "Disabled" }, - ], - multiple: false, - }, - ], - confirmText: "Are you sure you want to set per-user MFA for these users?", - multiPost: false, - }, - { - //tested - label: "Convert to Shared Mailbox", - type: "GET", - icon: , - url: "/api/ExecConvertToSharedMailbox", - data: { ID: "userPrincipalName" }, - confirmText: "Are you sure you want to convert this user to a shared mailbox?", - multiPost: false, - }, - { - label: "Convert to User Mailbox", - type: "GET", - icon: , - url: "/api/ExecConvertToSharedMailbox", - data: { ID: "userPrincipalName", ConvertToUser: true }, - confirmText: "Are you sure you want to convert this user to a user mailbox?", - multiPost: false, - }, - { - //tested - label: "Enable Online Archive", - type: "GET", - icon: , - url: "/api/ExecEnableArchive", - data: { ID: "userPrincipalName" }, - confirmText: "Are you sure you want to enable the online archive for this user?", - multiPost: false, - }, - { - //tested - label: "Set Out of Office", - type: "POST", - icon: , - url: "/api/ExecSetOoO", - data: { - userId: "userPrincipalName", - AutoReplyState: { value: "Enabled" }, - tenantFilter: "Tenant", - }, - fields: [{ type: "richText", name: "input", label: "Out of Office Message" }], - confirmText: "Are you sure you want to set the out of office?", - multiPost: false, - }, - - { - label: "Disable Out of Office", - type: "POST", - icon: , - url: "/api/ExecSetOoO", - data: { user: "userPrincipalName", AutoReplyState: "Disabled" }, - confirmText: "Are you sure you want to disable the out of office?", - multiPost: false, - }, - { - label: "Add to Group", - type: "POST", - icon: , - url: "/api/EditGroup", - data: { addMember: "userPrincipalName" }, - fields: [ - { - type: "autoComplete", - name: "groupId", - label: "Select a group to add the user to", - multiple: false, - creatable: false, - api: { - url: "/api/ListGroups", - labelField: "displayName", - valueField: "id", - addedField: { - groupType: "calculatedGroupType", - groupName: "displayName", - }, - queryKey: `groups-${tenant}`, - }, - }, - ], - confirmText: "Are you sure you want to add the user to this group?", - }, - { - label: "Disable Email Forwarding", - type: "POST", - url: "/api/ExecEmailForward", - icon: , - data: { - username: "userPrincipalName", - userid: "userPrincipalName", - ForwardOption: "!disabled", - }, - confirmText: "Are you sure you want to disable forwarding of this user's emails?", - multiPost: false, - }, - { - label: "Pre-provision OneDrive", - type: "POST", - icon: , - url: "/api/ExecOneDriveProvision", - data: { UserPrincipalName: "userPrincipalName" }, - confirmText: "Are you sure you want to pre-provision OneDrive for this user?", - multiPost: false, - }, - { - label: "Add OneDrive Shortcut", - type: "POST", - icon: , - url: "/api/ExecOneDriveShortCut", - data: { - username: "userPrincipalName", - userid: "id", - }, - fields: [ - { - type: "autoComplete", - name: "siteUrl", - label: "Select a Site", - multiple: false, - creatable: false, - api: { - url: "/api/ListSites", - data: { type: "SharePointSiteUsage", URLOnly: true }, - labelField: "webUrl", - valueField: "webUrl", - queryKey: `sharepointSites-${tenant}`, - }, - }, - ], - confirmText: "Select a SharePoint site to create a shortcut for:", - multiPost: false, - }, - { - label: "Block Sign In", - type: "GET", - icon: , - url: "/api/ExecDisableUser", - data: { ID: "id" }, - confirmText: "Are you sure you want to block the sign-in for this user?", - multiPost: false, - condition: (row) => row.accountEnabled, - }, - { - label: "Unblock Sign In", - type: "GET", - icon: , - url: "/api/ExecDisableUser", - data: { ID: "id", Enable: true }, - confirmText: "Are you sure you want to unblock sign-in for this user?", - multiPost: false, - condition: (row) => !row.accountEnabled, - }, - { - label: "Reset Password (Must Change)", - type: "GET", - icon: , - url: "/api/ExecResetPass", - data: { - MustChange: true, - ID: "userPrincipalName", - displayName: "displayName", - }, - confirmText: - "Are you sure you want to reset the password for this user? The user must change their password at next logon.", - multiPost: false, - }, - { - label: "Reset Password", - type: "GET", - icon: , - url: "/api/ExecResetPass", - data: { - MustChange: false, - ID: "userPrincipalName", - displayName: "displayName", - }, - confirmText: "Are you sure you want to reset the password for this user?", - multiPost: false, - }, - { - label: "Clear Immutable ID", - type: "GET", - icon: , - url: "/api/ExecClrImmId", - data: { - ID: "id", - }, - confirmText: "Are you sure you want to clear the Immutable ID for this user?", - multiPost: false, - condition: (row) => row.onPremisesSyncEnabled, - }, - { - label: "Revoke all user sessions", - type: "GET", - icon: , - url: "/api/ExecRevokeSessions", - data: { ID: "id", Username: "userPrincipalName" }, - confirmText: "Are you sure you want to revoke all sessions for this user?", - multiPost: false, - }, - { - label: "Delete User", - type: "GET", - icon: , - url: "/api/RemoveUser", - data: { ID: "id" }, - confirmText: "Are you sure you want to delete this user?", - multiPost: false, - }, - ]; - const offCanvas = { extendedInfoFields: [ "createdDateTime", // Created Date (UTC) @@ -336,7 +26,7 @@ const Page = () => { "id", // Unique ID "otherMails", // Alternate Email Addresses ], - actions: actions, + actions: CippUserActions(), }; return ( @@ -366,7 +56,7 @@ const Page = () => { $top: 999, }} apiDataKey="Results" - actions={actions} + actions={CippUserActions()} offCanvas={offCanvas} simpleColumns={[ "accountEnabled", diff --git a/src/pages/identity/administration/users/user/index.jsx b/src/pages/identity/administration/users/user/index.jsx index 4f0b2bb22ae5..3ed348320120 100644 --- a/src/pages/identity/administration/users/user/index.jsx +++ b/src/pages/identity/administration/users/user/index.jsx @@ -4,7 +4,7 @@ import { useRouter } from "next/router"; import { ApiGetCall } from "/src/api/ApiCall"; import CippFormSkeleton from "/src/components/CippFormPages/CippFormSkeleton"; import CalendarIcon from "@heroicons/react/24/outline/CalendarIcon"; -import { Check, Mail } from "@mui/icons-material"; +import { AdminPanelSettings, Check, Group, Mail } from "@mui/icons-material"; import { HeaderedTabbedLayout } from "../../../../../layouts/HeaderedTabbedLayout"; import tabOptions from "./tabOptions"; import { CippCopyToClipBoard } from "../../../../../components/CippComponents/CippCopyToClipboard"; @@ -17,27 +17,7 @@ import { CippTimeAgo } from "../../../../../components/CippComponents/CippTimeAg import { useEffect, useState } from "react"; import { usePopover } from "../../../../../hooks/use-popover"; import { useDialog } from "../../../../../hooks/use-dialog"; -import { EyeIcon, MagnifyingGlassIcon, TrashIcon } from "@heroicons/react/24/outline"; -import { - Archive, - Block, - Clear, - CloudDone, - Edit, - Email, - ForwardToInbox, - GroupAdd, - LockOpen, - LockPerson, - LockReset, - MeetingRoom, - NoMeetingRoom, - Password, - PersonOff, - PhonelinkLock, - PhonelinkSetup, - Shortcut, -} from "@mui/icons-material"; +import CippUserActions from "/src/components/CippComponents/CippUserActions"; const Page = () => { const popover = usePopover(); @@ -51,12 +31,23 @@ const Page = () => { setWaiting(true); } }, [userId]); + const userRequest = ApiGetCall({ url: `/api/ListUsers?UserId=${userId}&tenantFilter=${userSettingsDefaults.currentTenant}`, queryKey: `ListUsers-${userId}`, waiting: waiting, }); + const userMemberOf = ApiGetCall({ + url: "/api/ListGraphRequest", + data: { + Endpoint: `/users/${userId}/memberOf`, + tenantFilter: userSettingsDefaults.currentTenant, + $top: 99, + }, + queryKey: `UserMemberOf-${userId}`, + }); + const MFARequest = ApiGetCall({ url: "/api/ListGraphRequest", data: { @@ -356,288 +347,60 @@ const Page = () => { }, ]; } - const actions = [ - { - //tested - label: "View User", - link: "/identity/administration/users/user?userId=[id]", - multiPost: false, - icon: , - color: "success", - }, - { - //tested - label: "Edit User", - link: "/identity/administration/users/user/edit?userId=[id]", - icon: , - color: "success", - target: "_self", - }, - { - //tested - label: "Research Compromised Account", - type: "GET", - icon: , - link: "/identity/administration/users/user/bec?userId=[id]", - confirmText: "Are you sure you want to research this compromised account?", - multiPost: false, - }, - { - //tested - label: "Create Temporary Access Password", - type: "GET", - icon: , - url: "/api/ExecCreateTAP", - data: { ID: "userPrincipalName" }, - confirmText: "Are you sure you want to create a Temporary Access Password?", - multiPost: false, - }, - { - //tested - label: "Rerequire MFA registration", - type: "GET", - icon: , - url: "/api/ExecResetMFA", - data: { ID: "userPrincipalName" }, - confirmText: "Are you sure you want to reset MFA for this user?", - multiPost: false, - }, - { - //tested - label: "Send MFA Push", - type: "POST", - icon: , - url: "/api/ExecSendPush", - data: { UserEmail: "userPrincipalName" }, - confirmText: "Are you sure you want to send an MFA request?", - multiPost: false, - }, - { - //tested - label: "Set Per-User MFA", - type: "POST", - icon: , - url: "/api/ExecPerUserMFA", - data: { userId: "userPrincipalName" }, - fields: [ + const groupMembershipItems = userMemberOf.isSuccess + ? [ { - type: "autoComplete", - name: "State", - label: "State", - options: [ - { label: "Enforced", value: "Enforced" }, - { label: "Enabled", value: "Enabled" }, - { label: "Disabled", value: "Disabled" }, - ], - multiple: false, + id: 1, + cardLabelBox: { + cardLabelBoxHeader: , + }, + text: "Group Memberships", + subtext: "List of groups the user is a member of", + table: { + title: "Group Memberships", + hideTitle: true, + actions: [ + { + label: "Edit Group", + link: "/identity/administration/groups/edit?groupId=[id]", + }, + ], + data: userMemberOf?.data?.Results.filter( + (item) => item?.["@odata.type"] === "#microsoft.graph.group" + ), + simpleColumns: ["displayName", "groupTypes", "securityEnabled", "mailEnabled"], + }, }, - ], - confirmText: "Are you sure you want to set per-user MFA for these users?", - multiPost: false, - }, - { - //tested - label: "Convert to Shared Mailbox", - type: "GET", - icon: , - url: "/api/ExecConvertToSharedMailbox", - data: { ID: "userPrincipalName" }, - confirmText: "Are you sure you want to convert this user to a shared mailbox?", - multiPost: false, - }, - { - //tested - label: "Enable Online Archive", - type: "GET", - icon: , - url: "/api/ExecEnableArchive", - data: { ID: "userPrincipalName" }, - confirmText: "Are you sure you want to enable the online archive for this user?", - multiPost: false, - }, - { - //tested - label: "Set Out of Office", - type: "POST", - icon: , - url: "/api/ExecSetOoO", - data: { - userId: "userPrincipalName", - AutoReplyState: { value: "Enabled" }, - }, - fields: [{ type: "richText", name: "input", label: "Out of Office Message" }], - confirmText: "Are you sure you want to set the out of office?", - multiPost: false, - }, + ] + : []; - { - label: "Disable Out of Office", - type: "POST", - icon: , - url: "/api/ExecSetOoO", - data: { user: "userPrincipalName", AutoReplyState: "Disabled" }, - confirmText: "Are you sure you want to disable the out of office?", - multiPost: false, - }, - { - label: "Add to Group", - type: "POST", - icon: , - url: "/api/EditGroup", - data: { addMember: "userPrincipalName" }, - fields: [ + const roleMembershipItems = userMemberOf.isSuccess + ? [ { - type: "autoComplete", - name: "groupId", - label: "Select a group to add the user to", - multiple: false, - creatable: false, - api: { - url: "/api/ListGroups", - labelField: "displayName", - valueField: "id", - addedField: { - groupType: "calculatedGroupType", - groupName: "displayName", - }, - queryKey: `groups-${userSettingsDefaults.currentTenant}}`, + id: 1, + cardLabelBox: { + cardLabelBoxHeader: , }, - }, - ], - confirmText: "Are you sure you want to add the user to this group?", - }, - { - label: "Disable Email Forwarding", - type: "POST", - url: "/api/ExecEmailForward", - icon: , - data: { - username: "userPrincipalName", - userid: "userPrincipalName", - ForwardOption: "!disabled", - }, - confirmText: "Are you sure you want to disable forwarding of this user's emails?", - multiPost: false, - }, - { - label: "Pre-provision OneDrive", - type: "POST", - icon: , - url: "/api/ExecOneDriveProvision", - data: { UserPrincipalName: "userPrincipalName" }, - confirmText: "Are you sure you want to pre-provision OneDrive for this user?", - multiPost: false, - }, - { - label: "Add OneDrive Shortcut", - type: "POST", - icon: , - url: "/api/ExecOneDriveShortCut", - data: { - username: "userPrincipalName", - userid: "id", - }, - fields: [ - { - type: "autoComplete", - name: "siteUrl", - label: "Select a Site", - multiple: false, - creatable: false, - api: { - url: "/api/ListSites", - data: { type: "SharePointSiteUsage", URLOnly: true }, - labelField: "webUrl", - valueField: "webUrl", - queryKey: `sharepointSites-${userSettingsDefaults.currentTenant}}`, + text: "Roles", + subtext: "List of roles the user is a member of", + table: { + title: "Role Memberships", + hideTitle: true, + data: userMemberOf?.data?.Results.filter( + (item) => item?.["@odata.type"] === "#microsoft.graph.directoryRole" + ), + simpleColumns: ["displayName", "description"], }, }, - ], - confirmText: "Select a SharePoint site to create a shortcut for:", - multiPost: false, - }, - { - label: "Block Sign In", - type: "GET", - icon: , - url: "/api/ExecDisableUser", - data: { ID: "id" }, - confirmText: "Are you sure you want to block the sign-in for this user?", - multiPost: false, - }, - { - label: "Unblock Sign In", - type: "GET", - icon: , - url: "/api/ExecDisableUser", - data: { ID: "id", Enable: true }, - confirmText: "Are you sure you want to unblock sign-in for this user?", - multiPost: false, - }, - { - label: "Reset Password (Must Change)", - type: "GET", - icon: , - url: "/api/ExecResetPass", - data: { - MustChange: true, - ID: "userPrincipalName", - displayName: "displayName", - }, - confirmText: - "Are you sure you want to reset the password for this user? The user must change their password at next logon.", - multiPost: false, - }, - { - label: "Reset Password", - type: "GET", - icon: , - url: "/api/ExecResetPass", - data: { - MustChange: false, - ID: "userPrincipalName", - displayName: "displayName", - }, - confirmText: "Are you sure you want to reset the password for this user?", - multiPost: false, - }, - { - label: "Clear Immutable ID", - type: "GET", - icon: , - url: "/api/ExecClrImmId", - data: { - ID: "id", - }, - confirmText: "Are you sure you want to clear the Immutable ID for this user?", - multiPost: false, - }, - { - label: "Revoke all user sessions", - type: "GET", - icon: , - url: "/api/ExecRevokeSessions", - data: { ID: "id", Username: "userPrincipalName" }, - confirmText: "Are you sure you want to revoke all sessions for this user?", - multiPost: false, - }, - { - label: "Delete User", - type: "GET", - icon: , - url: "/api/RemoveUser", - data: { ID: "id" }, - confirmText: "Are you sure you want to delete this user?", - multiPost: false, - }, - ]; + ] + : []; return ( { items={mfaDevicesItems} isCollapsible={mfaDevicesItems.length > 0 ? true : false} /> + Memberships + 0 ? true : false} + /> + 0 ? true : false} + /> From a9150982de38dde6dffc12608c620339d25ec7f3 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 22 Jan 2025 22:12:36 -0500 Subject: [PATCH 58/98] Update index.jsx --- src/pages/identity/administration/users/user/index.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pages/identity/administration/users/user/index.jsx b/src/pages/identity/administration/users/user/index.jsx index 3ed348320120..80cbef9983e4 100644 --- a/src/pages/identity/administration/users/user/index.jsx +++ b/src/pages/identity/administration/users/user/index.jsx @@ -18,6 +18,7 @@ import { useEffect, useState } from "react"; import { usePopover } from "../../../../../hooks/use-popover"; import { useDialog } from "../../../../../hooks/use-dialog"; import CippUserActions from "/src/components/CippComponents/CippUserActions"; +import { PencilIcon } from "@heroicons/react/24/outline"; const Page = () => { const popover = usePopover(); @@ -362,6 +363,7 @@ const Page = () => { hideTitle: true, actions: [ { + icon: , label: "Edit Group", link: "/identity/administration/groups/edit?groupId=[id]", }, From 92047ef09eb409667277b18303b9f54c46443924 Mon Sep 17 00:00:00 2001 From: Roel van der Wegen Date: Thu, 23 Jan 2025 10:04:23 +0100 Subject: [PATCH 59/98] Fix parameter name --- src/data/alerts.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/alerts.json b/src/data/alerts.json index d7ee75a8aac4..d027afab9869 100644 --- a/src/data/alerts.json +++ b/src/data/alerts.json @@ -43,7 +43,7 @@ "requiresInput": true, "inputType": "textField", "inputLabel": "Enter quota percentage", - "inputName": "SharePointQuotaQuota", + "inputName": "SharePointQuota", "recommendedRunInterval": "4h" }, { From 4176e9e560f65016bd7797638e0159346fd528d1 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Thu, 23 Jan 2025 13:23:10 +0100 Subject: [PATCH 60/98] resolves pagination issue --- .../CippComponents/CippAutocomplete.jsx | 107 +++++++++++++----- 1 file changed, 78 insertions(+), 29 deletions(-) diff --git a/src/components/CippComponents/CippAutocomplete.jsx b/src/components/CippComponents/CippAutocomplete.jsx index 8cef03878f94..741fa8deec11 100644 --- a/src/components/CippComponents/CippAutocomplete.jsx +++ b/src/components/CippComponents/CippAutocomplete.jsx @@ -1,9 +1,9 @@ import { ArrowDropDown } from "@mui/icons-material"; import { Autocomplete, CircularProgress, createFilterOptions, TextField } from "@mui/material"; -import { ApiGetCall } from "../../api/ApiCall"; import { useEffect, useState } from "react"; import { useSettings } from "../../hooks/use-settings"; import { getCippError } from "../../utils/get-cipp-error"; +import { ApiGetCallWithPagination } from "../../api/ApiCall"; export const CippAutoComplete = (props) => { const { @@ -25,15 +25,29 @@ export const CippAutoComplete = (props) => { sx, ...other } = props; - const filter = createFilterOptions({ stringify: (option) => JSON.stringify(option) }); + const [usedOptions, setUsedOptions] = useState(options); const [getRequestInfo, setGetRequestInfo] = useState({ url: "", waiting: false, queryKey: "" }); + const filter = createFilterOptions({ + stringify: (option) => JSON.stringify(option), + }); - const actionGetRequest = ApiGetCall({ + // This is our paginated call + const actionGetRequest = ApiGetCallWithPagination({ ...getRequestInfo, }); const currentTenant = api?.tenantFilter ? api.tenantFilter : useSettings().currentTenant; + useEffect(() => { + if (actionGetRequest.isSuccess && !actionGetRequest.isFetching) { + const lastPage = actionGetRequest.data?.pages[actionGetRequest.data.pages.length - 1]; + const nextLinkExists = lastPage?.Metadata?.nextLink; + if (nextLinkExists) { + actionGetRequest.fetchNextPage(); + } + } + }, [actionGetRequest.data?.pages?.length, actionGetRequest.isFetching, api?.queryKey]); + useEffect(() => { if (api) { setGetRequestInfo({ @@ -46,52 +60,87 @@ export const CippAutoComplete = (props) => { queryKey: api.queryKey, }); } + }, [api, currentTenant]); + // After the data is fetched, combine and map it + useEffect(() => { if (actionGetRequest.isSuccess) { - const dataToMap = api.dataKey ? actionGetRequest.data?.[api.dataKey] : actionGetRequest.data; - if (!Array.isArray(dataToMap)) { + // E.g., allPages is an array of pages returned by the pagination + const allPages = actionGetRequest.data?.pages || []; + + // Helper to get nested data if you have something like "response.data.items" + const getNestedValue = (obj, path) => { + if (!path) return obj; + const keys = path.split("."); + let result = obj; + for (const key of keys) { + if (result && typeof result === "object" && key in result) { + result = result[key]; + } else { + return undefined; + } + } + return result; + }; + + // Flatten the results from all pages + const combinedResults = allPages.flatMap((page) => { + const nestedData = getNestedValue(page, api?.dataKey); + return nestedData !== undefined ? nestedData : []; + }); + + if (!Array.isArray(combinedResults)) { setUsedOptions([ { label: "Error: The API returned data we cannot map to this field", - value: "Error: The API returned data we cannot map to this field", + value: "Error", }, ]); - return; + } else { + // Convert each item into your { label, value, addedFields } shape + const convertedOptions = combinedResults.map((option) => { + const addedFields = {}; + if (api?.addedField) { + Object.keys(api.addedField).forEach((key) => { + addedFields[key] = option[api.addedField[key]]; + }); + } + + return { + label: + typeof api?.labelField === "function" + ? api.labelField(option) + : option[api?.labelField], + value: + typeof api?.valueField === "function" + ? api.valueField(option) + : option[api?.valueField], + addedFields, + }; + }); + setUsedOptions(convertedOptions); } - const convertedOptions = dataToMap.map((option) => { - const addedFields = {}; - if (api.addedField) { - Object.keys(api.addedField).forEach((key) => { - addedFields[key] = option[api.addedField[key]]; - }); - } - return { - label: - typeof api.labelField === "function" ? api.labelField(option) : option[api.labelField], - value: - typeof api.valueField === "function" ? api.valueField(option) : option[api.valueField], - addedFields: addedFields, - }; - }); - setUsedOptions(convertedOptions); } + if (actionGetRequest.isError) { setUsedOptions([{ label: getCippError(actionGetRequest.error), value: "error" }]); } - }, [api, actionGetRequest.data]); + }, [api, actionGetRequest.data, actionGetRequest.isSuccess, actionGetRequest.isError]); + const rand = Math.random().toString(36).substring(5); + return ( ) : ( ) } - isOptionEqualToValue={(option, value) => option.value === value.value} + isOptionEqualToValue={(option, val) => option.value === val.value} value={typeof value === "string" ? { label: value, value: value } : value} filterSelectedOptions disableClearable={disableClearable} @@ -100,12 +149,11 @@ export const CippAutoComplete = (props) => { filterOptions={(options, params) => { const filtered = filter(options, params); const isExisting = - options !== undefined && - options !== null && options?.length > 0 && - options?.some( + options.some( (option) => params.inputValue === option.value || params.inputValue === option.label ); + if (params.inputValue !== "" && creatable && !isExisting) { filtered.push({ label: `Add option: "${params.inputValue}"`, @@ -126,6 +174,7 @@ export const CippAutoComplete = (props) => { onChange={(event, newValue) => { if (Array.isArray(newValue)) { newValue = newValue.map((item) => { + // If user typed a new item or missing label if (item?.manual || !item?.label) { item = { label: item?.label ? item.value : item, From be59621813671dd41802cab0d1eec1a605b40a35 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 23 Jan 2025 13:44:56 -0500 Subject: [PATCH 61/98] Update index.jsx --- src/pages/identity/administration/users/user/index.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/identity/administration/users/user/index.jsx b/src/pages/identity/administration/users/user/index.jsx index 80cbef9983e4..37f3d396924f 100644 --- a/src/pages/identity/administration/users/user/index.jsx +++ b/src/pages/identity/administration/users/user/index.jsx @@ -356,7 +356,7 @@ const Page = () => { cardLabelBox: { cardLabelBoxHeader: , }, - text: "Group Memberships", + text: "Groups", subtext: "List of groups the user is a member of", table: { title: "Group Memberships", @@ -384,10 +384,10 @@ const Page = () => { cardLabelBox: { cardLabelBoxHeader: , }, - text: "Roles", + text: "Admin Roles", subtext: "List of roles the user is a member of", table: { - title: "Role Memberships", + title: "Admin Roles", hideTitle: true, data: userMemberOf?.data?.Results.filter( (item) => item?.["@odata.type"] === "#microsoft.graph.directoryRole" From 9408080c0151c94dd1f36e5daff510da320e5cde Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 23 Jan 2025 13:46:27 -0500 Subject: [PATCH 62/98] table maintenance new superadmin page --- .../CippCards/CippPropertyListCard.jsx | 6 +- .../CippComponents/CippApiDialog.jsx | 17 +- src/layouts/config.js | 5 + src/pages/cipp/advanced/table-maintenance.js | 413 ++++++++++++++++++ 4 files changed, 436 insertions(+), 5 deletions(-) create mode 100644 src/pages/cipp/advanced/table-maintenance.js diff --git a/src/components/CippCards/CippPropertyListCard.jsx b/src/components/CippCards/CippPropertyListCard.jsx index b5952305fa79..ff7cdcd92a64 100644 --- a/src/components/CippCards/CippPropertyListCard.jsx +++ b/src/components/CippCards/CippPropertyListCard.jsx @@ -151,7 +151,11 @@ export const CippPropertyListCard = (props) => { action: item, ready: true, }); - createDialog.handleOpen(); + if (item?.noConfirm) { + item.customFunction(item, data, {}); + } else { + createDialog.handleOpen(); + } } } disabled={handleActionDisabled(data, item)} diff --git a/src/components/CippComponents/CippApiDialog.jsx b/src/components/CippComponents/CippApiDialog.jsx index 282d8bab7b3b..70423a417045 100644 --- a/src/components/CippComponents/CippApiDialog.jsx +++ b/src/components/CippComponents/CippApiDialog.jsx @@ -38,6 +38,9 @@ export const CippApiDialog = (props) => { bulkRequest: api.multiPost === false, onResult: (result) => { setPartialResults((prevResults) => [...prevResults, result]); + if (api?.onSuccess) { + api.onSuccess(result); + } }, }); const actionGetRequest = ApiGetCall({ @@ -50,6 +53,9 @@ export const CippApiDialog = (props) => { bulkRequest: api.multiPost === false, onResult: (result) => { setPartialResults((prevResults) => [...prevResults, result]); + if (api?.onSuccess) { + api.onSuccess(result); + } }, }); @@ -58,6 +64,8 @@ export const CippApiDialog = (props) => { return api.dataFunction(row); } var newData = {}; + console.log("the received row", row); + console.log("the received dataObject", dataObject); if (api?.postEntireRow) { newData = row; @@ -85,6 +93,7 @@ export const CippApiDialog = (props) => { } }); } + console.log("output", newData); return newData; }; const tenantFilter = useSettings().currentTenant; @@ -209,10 +218,10 @@ export const CippApiDialog = (props) => { } useEffect(() => { if (api.noConfirm) { - formHook.handleSubmit(onSubmit)(); - createDialog.handleClose(); + formHook.handleSubmit(onSubmit)(); // Submits the form on mount + createDialog.handleClose(); // Closes the dialog after submitting } - }, [api.noConfirm]); + }, [api.noConfirm]); // Run effect only when api.noConfirm changes const handleClose = () => { createDialog.handleClose(); @@ -251,7 +260,7 @@ export const CippApiDialog = (props) => { Close diff --git a/src/layouts/config.js b/src/layouts/config.js index 39d5f0c8714e..7dceb0c4cf65 100644 --- a/src/layouts/config.js +++ b/src/layouts/config.js @@ -472,6 +472,11 @@ export const nativeMenuItems = [ path: "/cipp/advanced/timers", roles: ["superadmin"], }, + { + title: "Table Maintenance", + path: "/cipp/advanced/table-maintenance", + roles: ["superadmin"], + } ], }, ], diff --git a/src/pages/cipp/advanced/table-maintenance.js b/src/pages/cipp/advanced/table-maintenance.js new file mode 100644 index 000000000000..d736115b1636 --- /dev/null +++ b/src/pages/cipp/advanced/table-maintenance.js @@ -0,0 +1,413 @@ +import { Layout as DashboardLayout } from "/src/layouts/index.js"; +import { useEffect, useState } from "react"; +import { ApiPostCall } from "../../../api/ApiCall"; +import { CippPropertyListCard } from "/src/components/CippCards/CippPropertyListCard"; // Fixed import +import { CippDataTable } from "/src/components/CippTable/CippDataTable"; // Fixed import +import { useDialog } from "../../../hooks/use-dialog"; +import { + Box, + Container, + Stack, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + IconButton, + Button, + SvgIcon, + Tooltip, + Typography, +} from "@mui/material"; +import { MagnifyingGlassIcon, PencilIcon, TrashIcon } from "@heroicons/react/24/outline"; +import { Add, AddCircle, RemoveCircle, Sync, WarningAmber } from "@mui/icons-material"; +import CippFormComponent from "../../../components/CippComponents/CippFormComponent"; +import { useForm, useWatch } from "react-hook-form"; +import { CippApiDialog } from "../../../components/CippComponents/CippApiDialog"; +import { Grid } from "@mui/system"; + +const CustomAddEditRowDialog = ({ formControl, open, onClose, onSubmit, defaultValues }) => { + const fields = useWatch({ control: formControl.control, name: "fields" }); + + useEffect(() => { + if (open) { + console.log(defaultValues); + formControl.reset({ + fields: defaultValues.fields || [], + }); + } + }, [open, defaultValues]); + + const addField = () => { + formControl.reset({ + fields: [...fields, { name: "", value: "" }], + }); + }; + + const removeField = (index) => { + const newFields = fields.filter((_, i) => i !== index); + formControl.reset({ fields: newFields }); + }; + + return ( + + Add/Edit Row + + + {Array.isArray(fields) && fields?.length > 0 && ( + <> + {fields.map((field, index) => ( + + + + + + + + removeField(index)}> + + + + ))} + + )} + + + + + + + + + ); +}; + +const Page = () => { + const pageTitle = "Table Maintenance"; + const apiUrl = "/api/ExecAzBobbyTables"; + const [tables, setTables] = useState([]); + const [selectedTable, setSelectedTable] = useState(null); + const [tableData, setTableData] = useState([]); + const addTableDialog = useDialog(); // Add dialog for adding table + const deleteTableDialog = useDialog(); // Add dialog for deleting table + const addEditRowDialog = useDialog(); // Add dialog for adding/editing row + const [defaultAddEditValues, setDefaultAddEditValues] = useState({}); + const formControl = useForm({ + mode: "onChange", + }); + + const addEditFormControl = useForm({ + mode: "onChange", + }); + + const tableFilter = useWatch({ control: formControl.control, name: "tableFilter" }); + + const fetchTables = ApiPostCall({ + queryKey: "CippTables", + onResult: (result) => setTables(result), + }); + + const fetchTableData = ApiPostCall({ + queryKey: "CippTableData", + onResult: (result) => { + setTableData(result); + }, + }); + + const handleTableSelect = (tableName) => { + setSelectedTable(tableName); + fetchTableData.mutate({ + url: apiUrl, + data: { + FunctionName: "Get-AzDataTableEntity", + TableName: tableName, + Parameters: { First: 1000 }, + }, + }); + }; + + const handleRefresh = () => { + if (selectedTable) { + fetchTableData.mutate({ + url: apiUrl, + data: { + FunctionName: "Get-AzDataTableEntity", + TableName: selectedTable, + Parameters: { First: 1000 }, + }, + }); + } + }; + + const tableRowAction = ApiPostCall({ + queryKey: "CippTableRowAction", + onResult: handleRefresh, + }); + + const handleTableRefresh = () => { + fetchTables.mutate({ url: apiUrl, data: { FunctionName: "Get-AzDataTable", Parameters: {} } }); + }; + + useEffect(() => { + handleTableRefresh(); + }, []); + + const actionItems = tables + .filter( + (table) => + tableFilter === "" || + tableFilter === undefined || + table.toLowerCase().includes(tableFilter.toLowerCase()) + ) + .map((table) => ({ + label: `${table}`, + customFunction: () => { + setTableData([]); + handleTableSelect(table); + }, + noConfirm: true, + })); + + const propertyItems = [ + { + label: "", + value: ( + + + + + + + ), + }, + ]; + + const getTableFields = () => { + if (tableData.length === 0) return []; + const sampleRow = tableData[0]; + return Object.keys(sampleRow) + .filter((key) => key !== "ETag" && key !== "Timestamp") + .map((key) => ({ + name: key, + label: key, + type: "textField", + required: false, + })); + }; + + return ( + + + {pageTitle} + + + + a.label.localeCompare(b.label))} + isFetching={fetchTables.isPending} + cardSx={{ maxHeight: "calc(100vh - 170px)", overflow: "auto" }} + actionButton={ + + + + + + + + + + + + + + + + + } + /> + + + {selectedTable && ( + + + + + + } + actions={[ + { + label: "Edit", + type: "POST", + icon: ( + + + + ), + customFunction: (row) => { + setDefaultAddEditValues({ + fields: Object.keys(row) + .filter((key) => key !== "ETag" && key !== "Timestamp") + .map((key) => ({ name: key, value: row[key] })), + }); + addEditRowDialog.handleOpen(); + }, + noConfirm: true, + }, + { + label: "Delete", + type: "POST", + icon: ( + + + + ), + url: apiUrl, + data: { + FunctionName: "Remove-AzDataTableEntity", + TableName: `!${selectedTable}`, + Parameters: { + Entity: { RowKey: "RowKey", PartitionKey: "PartitionKey", ETag: "ETag" }, + }, + }, + onSuccess: handleRefresh, + confirmText: "Do you want to delete this row?", + }, + ]} + /> + + )} + + + { + handleTableRefresh(); + }, + }} + /> + + + + Are you sure you want to delete this table? This is a destructive action that cannot + be undone. + + + ), + type: "POST", + data: { FunctionName: "Remove-AzDataTable", TableName: selectedTable, Parameters: {} }, + onSuccess: () => { + setSelectedTable(null); + setTableData([]); + handleTableRefresh(); + }, + }} + /> + { + const payload = data.fields.reduce((acc, field) => { + acc[field.name] = field.value; + return acc; + }, {}); + tableRowAction.mutate({ + url: apiUrl, + data: { + FunctionName: "Add-AzDataTableEntity", + TableName: selectedTable, + Parameters: { Entity: payload, Force: true }, + }, + onSuccess: handleRefresh, + }); + addEditRowDialog.handleClose(); + }} + defaultValues={defaultAddEditValues} + /> + + ); +}; + +Page.getLayout = (page) => {page}; + +export default Page; From 0c210ba488bae7f55c8f61553a67416bc7276544 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 23 Jan 2025 14:07:32 -0500 Subject: [PATCH 63/98] field types --- src/pages/cipp/advanced/table-maintenance.js | 71 ++++++++++++++++---- 1 file changed, 59 insertions(+), 12 deletions(-) diff --git a/src/pages/cipp/advanced/table-maintenance.js b/src/pages/cipp/advanced/table-maintenance.js index d736115b1636..20e7594c9f64 100644 --- a/src/pages/cipp/advanced/table-maintenance.js +++ b/src/pages/cipp/advanced/table-maintenance.js @@ -17,6 +17,8 @@ import { SvgIcon, Tooltip, Typography, + MenuItem, + Select, } from "@mui/material"; import { MagnifyingGlassIcon, PencilIcon, TrashIcon } from "@heroicons/react/24/outline"; import { Add, AddCircle, RemoveCircle, Sync, WarningAmber } from "@mui/icons-material"; @@ -30,16 +32,15 @@ const CustomAddEditRowDialog = ({ formControl, open, onClose, onSubmit, defaultV useEffect(() => { if (open) { - console.log(defaultValues); formControl.reset({ fields: defaultValues.fields || [], }); } - }, [open, defaultValues]); + }, [open, defaultValues, formControl]); const addField = () => { formControl.reset({ - fields: [...fields, { name: "", value: "" }], + fields: [...fields, { name: "", value: "", type: "textField" }], }); }; @@ -48,6 +49,11 @@ const CustomAddEditRowDialog = ({ formControl, open, onClose, onSubmit, defaultV formControl.reset({ fields: newFields }); }; + const handleTypeChange = (index, newType) => { + const newFields = fields.map((field, i) => (i === index ? { ...field, type: newType } : field)); + formControl.reset({ fields: newFields }); + }; + return ( Add/Edit Row @@ -65,14 +71,36 @@ const CustomAddEditRowDialog = ({ formControl, open, onClose, onSubmit, defaultV label="Name" /> - + + + + { + if (field.type === "switch") { + return { ml: 2 }; + } else if (field.type === "number") { + return { width: "100%" }; + } else { + return {}; + } + }} /> + removeField(index)}> @@ -203,12 +231,21 @@ const Page = () => { const sampleRow = tableData[0]; return Object.keys(sampleRow) .filter((key) => key !== "ETag" && key !== "Timestamp") - .map((key) => ({ - name: key, - label: key, - type: "textField", - required: false, - })); + .map((key) => { + const value = sampleRow[key]; + let type = "textField"; + if (typeof value === "number") { + type = "number"; + } else if (typeof value === "boolean") { + type = "switch"; + } + return { + name: key, + label: key, + type: type, + required: false, + }; + }); }; return ( @@ -273,6 +310,7 @@ const Page = () => { fields: getTableFields().map((field) => ({ name: field?.name, value: "", + type: field?.type, })), }); addEditRowDialog.handleOpen(); @@ -313,7 +351,16 @@ const Page = () => { setDefaultAddEditValues({ fields: Object.keys(row) .filter((key) => key !== "ETag" && key !== "Timestamp") - .map((key) => ({ name: key, value: row[key] })), + .map((key) => { + const value = row[key]; + let type = "textField"; + if (typeof value === "number") { + type = "number"; + } else if (typeof value === "boolean") { + type = "switch"; + } + return { name: key, value: value, type: type }; + }), }); addEditRowDialog.handleOpen(); }, From 8782844c32e8dabdc7beb5d3530efb362ac0d40f Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 23 Jan 2025 15:53:09 -0500 Subject: [PATCH 64/98] table maintenance tweaks add filters --- src/pages/cipp/advanced/table-maintenance.js | 104 +++++++++++++++++-- 1 file changed, 93 insertions(+), 11 deletions(-) diff --git a/src/pages/cipp/advanced/table-maintenance.js b/src/pages/cipp/advanced/table-maintenance.js index 20e7594c9f64..df6592df96f3 100644 --- a/src/pages/cipp/advanced/table-maintenance.js +++ b/src/pages/cipp/advanced/table-maintenance.js @@ -19,6 +19,7 @@ import { Typography, MenuItem, Select, + Alert, } from "@mui/material"; import { MagnifyingGlassIcon, PencilIcon, TrashIcon } from "@heroicons/react/24/outline"; import { Add, AddCircle, RemoveCircle, Sync, WarningAmber } from "@mui/icons-material"; @@ -26,6 +27,7 @@ import CippFormComponent from "../../../components/CippComponents/CippFormCompon import { useForm, useWatch } from "react-hook-form"; import { CippApiDialog } from "../../../components/CippComponents/CippApiDialog"; import { Grid } from "@mui/system"; +import CippButtonCard from "../../../components/CippCards/CippButtonCard"; const CustomAddEditRowDialog = ({ formControl, open, onClose, onSubmit, defaultValues }) => { const fields = useWatch({ control: formControl.control, name: "fields" }); @@ -114,8 +116,12 @@ const CustomAddEditRowDialog = ({ formControl, open, onClose, onSubmit, defaultV - - + + ); @@ -131,14 +137,23 @@ const Page = () => { const deleteTableDialog = useDialog(); // Add dialog for deleting table const addEditRowDialog = useDialog(); // Add dialog for adding/editing row const [defaultAddEditValues, setDefaultAddEditValues] = useState({}); + const [tableFilterParams, setTableFilterParams] = useState({ First: 1000 }); const formControl = useForm({ mode: "onChange", }); + const [accordionExpanded, setAccordionExpanded] = useState(false); const addEditFormControl = useForm({ mode: "onChange", }); + const filterFormControl = useForm({ + mode: "onChange", + defaultValues: { + First: 1000, + }, + }); + const tableFilter = useWatch({ control: formControl.control, name: "tableFilter" }); const fetchTables = ApiPostCall({ @@ -160,7 +175,7 @@ const Page = () => { data: { FunctionName: "Get-AzDataTableEntity", TableName: tableName, - Parameters: { First: 1000 }, + Parameters: filterFormControl.getValues(), }, }); }; @@ -172,7 +187,7 @@ const Page = () => { data: { FunctionName: "Get-AzDataTableEntity", TableName: selectedTable, - Parameters: { First: 1000 }, + Parameters: tableFilterParams, }, }); } @@ -187,6 +202,20 @@ const Page = () => { fetchTables.mutate({ url: apiUrl, data: { FunctionName: "Get-AzDataTable", Parameters: {} } }); }; + const getSelectedProps = (data) => { + if (data?.Property && data?.Property.length > 0) { + var selectedProps = ["ETag", "PartitionKey", "RowKey"]; + data?.Property.map((prop) => { + if (selectedProps.indexOf(prop.value) === -1) { + selectedProps.push(prop.value); + } + }); + return selectedProps; + } else { + return []; + } + }; + useEffect(() => { handleTableRefresh(); }, []); @@ -211,16 +240,11 @@ const Page = () => { { label: "", value: ( - + - + ), }, @@ -253,6 +277,10 @@ const Page = () => { {pageTitle} + + This page allows you to view and manage data in Azure Tables. This is advanced functionality + that should only be used when directed by CyberDrain support. + { {selectedTable && ( + { + var properties = getSelectedProps(data); + setTableFilterParams({ ...data, Property: properties }); + handleRefresh(); + setAccordionExpanded(false); + })} + > + Apply Filters + + } + > + + + ({ + label: field?.label, + value: field?.name, + }))} + /> + + + + + + Date: Thu, 23 Jan 2025 23:26:55 +0100 Subject: [PATCH 65/98] dialogAfterEffect Added --- .../CippComponents/CippApiDialog.jsx | 22 ++++++++++++++----- .../CippStandards/CippStandardsSideBar.jsx | 6 +++++ 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/components/CippComponents/CippApiDialog.jsx b/src/components/CippComponents/CippApiDialog.jsx index 70423a417045..834cb3e958dc 100644 --- a/src/components/CippComponents/CippApiDialog.jsx +++ b/src/components/CippComponents/CippApiDialog.jsx @@ -9,7 +9,16 @@ import { useSettings } from "../../hooks/use-settings"; import CippFormComponent from "./CippFormComponent"; export const CippApiDialog = (props) => { - const { createDialog, title, fields, api, row = {}, relatedQueryKeys, ...other } = props; + const { + createDialog, + title, + fields, + api, + row = {}, + relatedQueryKeys, + dialogAfterEffect, + ...other + } = props; const router = useRouter(); const [addedFieldData, setAddedFieldData] = useState({}); const [partialResults, setPartialResults] = useState([]); @@ -64,8 +73,6 @@ export const CippApiDialog = (props) => { return api.dataFunction(row); } var newData = {}; - console.log("the received row", row); - console.log("the received dataObject", dataObject); if (api?.postEntireRow) { newData = row; @@ -93,7 +100,6 @@ export const CippApiDialog = (props) => { } }); } - console.log("output", newData); return newData; }; const tenantFilter = useSettings().currentTenant; @@ -138,6 +144,7 @@ export const CippApiDialog = (props) => { data: arrayOfObjects, }); } + return; } @@ -191,7 +198,12 @@ export const CippApiDialog = (props) => { }); } }; - + //add a useEffect, when dialogAfterEffect exists, and the post or get request is successful, run the dialogAfterEffect function + useEffect(() => { + if (dialogAfterEffect && (actionPostRequest.isSuccess || actionGetRequest.isSuccess)) { + dialogAfterEffect(actionPostRequest.data.data || actionGetRequest.data); + } + }, [actionPostRequest.isSuccess, actionGetRequest.isSuccess]); const formHook = useForm(); const onSubmit = (data) => handleActionClick(row, api, data); const selectedType = api.type === "POST" ? actionPostRequest : actionGetRequest; diff --git a/src/components/CippStandards/CippStandardsSideBar.jsx b/src/components/CippStandards/CippStandardsSideBar.jsx index b65a83299312..c6a405de636f 100644 --- a/src/components/CippStandards/CippStandardsSideBar.jsx +++ b/src/components/CippStandards/CippStandardsSideBar.jsx @@ -75,6 +75,10 @@ const CippStandardsSideBar = ({ edit, }) => { const [currentStep, setCurrentStep] = useState(0); + const [savedItem, setSavedItem] = useState(null); + const dialogAfterEffect = (id) => { + setSavedItem(id); + }; const watchForm = useWatch({ control: formControl.control }); @@ -208,6 +212,7 @@ const CippStandardsSideBar = ({ dialogAfterEffect(data.id)} createDialog={createDialog} title="Add Standard" api={{ @@ -223,6 +228,7 @@ const CippStandardsSideBar = ({ templateName: "templateName", standards: "standards", ...(edit ? { GUID: "GUID" } : {}), + ...(savedItem ? { GUID: savedItem } : {}), runManually: "runManually", }, }} From 79b3f6455fe2bdbcf644fac540b596bf50441d70 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 23 Jan 2025 17:37:31 -0500 Subject: [PATCH 66/98] add enter key support for barcode scanners --- src/components/CippWizard/CippWizardCSVImport.jsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/CippWizard/CippWizardCSVImport.jsx b/src/components/CippWizard/CippWizardCSVImport.jsx index dade62abe5d9..2bb6090c32a0 100644 --- a/src/components/CippWizard/CippWizardCSVImport.jsx +++ b/src/components/CippWizard/CippWizardCSVImport.jsx @@ -43,7 +43,6 @@ export const CippWizardCSVImport = (props) => { const handleAddItem = () => { const newRowData = formControl.getValues("addrow"); if (newRowData === undefined) return false; - const newTableData = [...tableData, newRowData]; setTableData(newTableData); setOpen(false); @@ -87,6 +86,14 @@ export const CippWizardCSVImport = (props) => { label={getCippTranslation(field)} type="textField" formControl={formControl} + onKeyDown={(e) => { + if (e.key === "Enter") { + handleAddItem(); + setTimeout(() => { + formControl.setValue(`addrow.${field}`, ""); + }, 500); + } + }} /> From 9938a37be4041100d5f4823ae1eb7af73764d08d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Thu, 23 Jan 2025 23:51:58 +0100 Subject: [PATCH 67/98] prevent empty input submission in CSV import on Enter key press --- src/components/CippWizard/CippWizardCSVImport.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/CippWizard/CippWizardCSVImport.jsx b/src/components/CippWizard/CippWizardCSVImport.jsx index 2bb6090c32a0..6f665df4523b 100644 --- a/src/components/CippWizard/CippWizardCSVImport.jsx +++ b/src/components/CippWizard/CippWizardCSVImport.jsx @@ -88,6 +88,7 @@ export const CippWizardCSVImport = (props) => { formControl={formControl} onKeyDown={(e) => { if (e.key === "Enter") { + if (e.target.value === "") return false; handleAddItem(); setTimeout(() => { formControl.setValue(`addrow.${field}`, ""); From ff2f09ce6a400ca3cefe1bde008384acde183453 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Fri, 24 Jan 2025 10:34:04 +0100 Subject: [PATCH 68/98] open up darkweb searches --- src/layouts/config.js | 18 +++++++++--------- src/utils/get-cipp-formatting.js | 6 +++--- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/layouts/config.js b/src/layouts/config.js index 7dceb0c4cf65..d9685ba29de4 100644 --- a/src/layouts/config.js +++ b/src/layouts/config.js @@ -424,14 +424,14 @@ export const nativeMenuItems = [ { title: "Message Viewer", path: "/email/tools/message-viewer" }, ], }, - // { - // title: "Dark Web Tools", - // path: "/tools/darkweb", - // items: [ - // { title: "Tenant Breach Lookup", path: "/tools/tenantbreachlookup" }, - // { title: "Breach Lookup", path: "/tools/breachlookup" }, - // ], - // }, + { + title: "Dark Web Tools", + path: "/tools/darkweb", + items: [ + { title: "Tenant Breach Lookup", path: "/tools/tenantbreachlookup" }, + { title: "Breach Lookup", path: "/tools/breachlookup" }, + ], + }, { title: "Template Library", path: "/tools/templatelib", @@ -476,7 +476,7 @@ export const nativeMenuItems = [ title: "Table Maintenance", path: "/cipp/advanced/table-maintenance", roles: ["superadmin"], - } + }, ], }, ], diff --git a/src/utils/get-cipp-formatting.js b/src/utils/get-cipp-formatting.js index 916c9d461dc3..d8e7ff471c6d 100644 --- a/src/utils/get-cipp-formatting.js +++ b/src/utils/get-cipp-formatting.js @@ -203,8 +203,8 @@ export const getCippFormatting = (data, cellName, type, canReceive) => { ? data.join(", ") : data.map((item) => ( )); @@ -212,7 +212,7 @@ export const getCippFormatting = (data, cellName, type, canReceive) => { return isText ? ( data ) : ( - + ); } } From bcdf9f5ae769753ab0623eb5a051953a1eb443f1 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Fri, 24 Jan 2025 12:40:19 +0100 Subject: [PATCH 69/98] fixes incorrect reporting --- src/pages/tenant/standards/bpa-report/view.js | 26 ++++++++----------- src/pages/tools/breachlookup/index.js | 7 +++++ 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/pages/tenant/standards/bpa-report/view.js b/src/pages/tenant/standards/bpa-report/view.js index b7db6c926e4e..b83ab23d8e71 100644 --- a/src/pages/tenant/standards/bpa-report/view.js +++ b/src/pages/tenant/standards/bpa-report/view.js @@ -22,6 +22,7 @@ import { CippImageCard } from "../../../../components/CippCards/CippImageCard"; import _ from "lodash"; const Page = () => { const router = useRouter(); + const { id } = router.query; const [blockCards, setBlockCards] = useState([]); const [layoutMode, setLayoutMode] = useState("Table"); const bpaTemplateList = ApiGetCall({ @@ -33,8 +34,9 @@ const Page = () => { url: "/api/listBPA", data: { tenantFilter: tenantFilter, + report: id, }, - queryKey: "ListBPA", + queryKey: `ListBPA-${id}-${tenantFilter}`, }); const tenantInfo = ApiGetCall({ url: "/api/ListTenants", @@ -51,7 +53,6 @@ const Page = () => { setLayoutMode(bpaTemplate.Style); if (bpaTemplate.Style === "Tenant") { const frontendFields = bpaTemplate.Data.map((block) => block.FrontendFields[0]); - if (bpaData.isSuccess) { const tenantId = tenantInfo?.data.find( (tenant) => tenant?.defaultDomainName === tenantFilter @@ -62,7 +63,7 @@ const Page = () => { //instead of this, use lodash to get the data for blockData const blockData = _.get(tenantData, field.value) ? _.get(tenantData, field.value) - : ["No Data"]; + : undefined; return { name: field.name, value: field.value, @@ -92,21 +93,12 @@ const Page = () => { //sometimes the subField contains a space. Only take the first part of the subField if it does. subField?.value?.includes(" ") ? subField.value.split(" ")[0] : subField.value ); + tenantData = Array.isArray(tenantData) ? tenantData : [tenantData]; //filter down tenantData to only the fields listOfFrontEndFields tenantData = tenantData.map((data) => { - const filteredData = {}; listOfFrontEndFields.unshift("Tenant"); - listOfFrontEndFields.forEach((field) => { - //we need to get the correct key, but the key is nested and can contain dots, or []. So we use lodash get to get the correct key. - const dataField = _.get(data, field) ? _.get(data, field) : "No Data"; - if (dataField === "FAILED") { - filteredData[field] = "Failed"; - } else { - filteredData[field] = dataField; - } - }); - return filteredData; + return data; }); const cards = { simpleColumns: listOfFrontEndFields, @@ -189,7 +181,11 @@ const Page = () => { } > - {block.formatter === "String" ? ( + {block.data === undefined ? ( + + No data has been found for this report. + + ) : block.formatter === "String" ? ( {block.data} diff --git a/src/pages/tools/breachlookup/index.js b/src/pages/tools/breachlookup/index.js index b7da246bd174..d50e98f137a7 100644 --- a/src/pages/tools/breachlookup/index.js +++ b/src/pages/tools/breachlookup/index.js @@ -8,6 +8,7 @@ import { Link, Chip, Avatar, + Alert, } from "@mui/material"; import { Layout as DashboardLayout } from "/src/layouts/index.js"; import { useForm, useWatch } from "react-hook-form"; @@ -54,6 +55,12 @@ const Page = () => { + + + This page is in beta and may not always give expected results. + + + From 31601b989dda92491a05b6e183a23a05783f3ca2 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Fri, 24 Jan 2025 12:52:43 +0100 Subject: [PATCH 70/98] updated text for alerts --- src/pages/tenant/standards/bpa-report/view.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/pages/tenant/standards/bpa-report/view.js b/src/pages/tenant/standards/bpa-report/view.js index b83ab23d8e71..2760fdc0a446 100644 --- a/src/pages/tenant/standards/bpa-report/view.js +++ b/src/pages/tenant/standards/bpa-report/view.js @@ -9,6 +9,7 @@ import { SvgIcon, Skeleton, Chip, + Alert, } from "@mui/material"; import Head from "next/head"; import { ArrowLeftIcon } from "@mui/x-date-pickers"; @@ -182,9 +183,11 @@ const Page = () => { } > {block.data === undefined ? ( - - No data has been found for this report. - + + No data has been found for this item. This tenant might not be licensed + for this feature, or data collection failed. Please check the logs for + more information. + ) : block.formatter === "String" ? ( {block.data} From 9cbe81d4d47f73eb65a0ed0db196e9088bde6992 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 24 Jan 2025 15:41:04 -0500 Subject: [PATCH 71/98] bugfixes and tweaks --- .../CippComponents/CippAutocomplete.jsx | 3 +- .../CippSettings/CippGDAPResults.jsx | 4 +-- .../CippSettings/CippPermissionCheck.jsx | 35 +++++++++++++++++-- .../CippTable/CIPPTableToptoolbar.js | 2 +- src/pages/cipp/advanced/table-maintenance.js | 2 ++ src/utils/get-cipp-formatting.js | 2 +- 6 files changed, 41 insertions(+), 7 deletions(-) diff --git a/src/components/CippComponents/CippAutocomplete.jsx b/src/components/CippComponents/CippAutocomplete.jsx index 741fa8deec11..8ecfd6aee8cd 100644 --- a/src/components/CippComponents/CippAutocomplete.jsx +++ b/src/components/CippComponents/CippAutocomplete.jsx @@ -22,6 +22,7 @@ export const CippAutoComplete = (props) => { onChange, onCreateOption, required = false, + isFetching = false, sx, ...other } = props; @@ -132,7 +133,7 @@ export const CippAutoComplete = (props) => { return ( diff --git a/src/components/CippSettings/CippGDAPResults.jsx b/src/components/CippSettings/CippGDAPResults.jsx index 99820ad401eb..c86c24939417 100644 --- a/src/components/CippSettings/CippGDAPResults.jsx +++ b/src/components/CippSettings/CippGDAPResults.jsx @@ -115,7 +115,7 @@ export const CippGDAPResults = (props) => { ) : ( <> - {gdapTests.map((test) => { + {gdapTests.map((test, index) => { var matchedResults = results?.Results?.[test.resultProperty]?.filter((item) => new RegExp(test.match)?.test(item?.[test.matchProperty]) ); @@ -128,7 +128,7 @@ export const CippGDAPResults = (props) => { } return ( - + {testResult ? : } diff --git a/src/components/CippSettings/CippPermissionCheck.jsx b/src/components/CippSettings/CippPermissionCheck.jsx index 553712441188..5ad0f5d0a502 100644 --- a/src/components/CippSettings/CippPermissionCheck.jsx +++ b/src/components/CippSettings/CippPermissionCheck.jsx @@ -1,10 +1,20 @@ -import { Box, Button, Chip, Stack, SvgIcon, Typography } from "@mui/material"; +import { + Alert, + Box, + Button, + Chip, + Collapse, + IconButton, + Stack, + SvgIcon, + Typography, +} from "@mui/material"; import CippButtonCard from "/src/components/CippCards/CippButtonCard"; import { ApiGetCall } from "/src/api/ApiCall"; import { useEffect, useState } from "react"; import { CippPermissionResults } from "./CippPermissionResults"; import { CippGDAPResults } from "./CippGDAPResults"; -import { Sync } from "@mui/icons-material"; +import { Close, Sync } from "@mui/icons-material"; import { CippTenantResults } from "./CippTenantResults"; import { CippTimeAgo } from "../CippComponents/CippTimeAgo"; import { Description } from "@mui/icons-material"; @@ -14,6 +24,7 @@ const CippPermissionCheck = (props) => { const [skipCache, setSkipCache] = useState(false); const [cardIcon, setCardIcon] = useState(null); const [offcanvasVisible, setOffcanvasVisible] = useState(false); + const [showAlertMessage, setShowAlertMessage] = useState(true); var showDetails = true; if (type === "Tenants") { @@ -130,6 +141,26 @@ const CippPermissionCheck = (props) => { > {(executeCheck.isSuccess || executeCheck.isLoading) && ( <> + {executeCheck.data?.Metadata?.AlertMessage && ( + + setShowAlertMessage(false)} + > + + + } + > + {executeCheck.data.Metadata.AlertMessage} + + + )} {type === "Permissions" && ( {actions - ?.filter((action) => !action.link) + ?.filter((action) => !action.link && !action?.hideBulk) .map((action, index) => ( { addEditRowDialog.handleOpen(); }, noConfirm: true, + hideBulk: true, }, { label: "Delete", @@ -466,6 +467,7 @@ const Page = () => { }, onSuccess: handleRefresh, confirmText: "Do you want to delete this row?", + multiPost: false, }, ]} /> diff --git a/src/utils/get-cipp-formatting.js b/src/utils/get-cipp-formatting.js index 916c9d461dc3..5eb769224d92 100644 --- a/src/utils/get-cipp-formatting.js +++ b/src/utils/get-cipp-formatting.js @@ -212,7 +212,7 @@ export const getCippFormatting = (data, cellName, type, canReceive) => { return isText ? ( data ) : ( - + ); } } From 837429667843ce216073a0f71f19f239da88fcc5 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 24 Jan 2025 15:47:20 -0500 Subject: [PATCH 72/98] Update get-cipp-formatting.js --- src/utils/get-cipp-formatting.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/get-cipp-formatting.js b/src/utils/get-cipp-formatting.js index 5eb769224d92..6ceaaf8563f3 100644 --- a/src/utils/get-cipp-formatting.js +++ b/src/utils/get-cipp-formatting.js @@ -212,7 +212,7 @@ export const getCippFormatting = (data, cellName, type, canReceive) => { return isText ? ( data ) : ( - + ); } } From 025b92410feb97cb849fe60e66e7f808ebb20b8a Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 24 Jan 2025 16:54:07 -0500 Subject: [PATCH 73/98] add filter to signin report --- .../identity/reports/signin-report/index.js | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/src/pages/identity/reports/signin-report/index.js b/src/pages/identity/reports/signin-report/index.js index 4f89e7a4b0b4..6304b8fa0da6 100644 --- a/src/pages/identity/reports/signin-report/index.js +++ b/src/pages/identity/reports/signin-report/index.js @@ -1,5 +1,8 @@ import { Layout as DashboardLayout } from "/src/layouts/index.js"; import { CippTablePage } from "/src/components/CippComponents/CippTablePage.jsx"; +import { useState } from "react"; +import { Button, Grid, TextField, Switch, FormControlLabel } from "@mui/material"; +import CippButtonCard from "/src/components/CippCards/CippButtonCard"; const Page = () => { const pageTitle = "Sign Ins Report"; @@ -16,14 +19,93 @@ const Page = () => { "locationcipp", ]; + const [filterValues, setFilterValues] = useState({ + Days: 7, + filter: "", + failedLogonsOnly: false, + FailureThreshold: 0, + }); + + const [appliedFilters, setAppliedFilters] = useState(filterValues); + + const handleFilterChange = (event) => { + const { name, value, type, checked } = event.target; + setFilterValues({ + ...filterValues, + [name]: type === "checkbox" ? checked : value, + }); + }; + + const handleFilterSubmit = () => { + setAppliedFilters(filterValues); + }; + + const tableFilter = ( + + + + + + + + + + + } + label="Failed Logons Only" + /> + + {filterValues.failedLogonsOnly && ( + + + + )} + + + + + + ); + return ( <> ); From 4b0b30e45900c49bcf4f3d8d4f4e923ab51239dd Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 24 Jan 2025 19:18:49 -0500 Subject: [PATCH 74/98] view user improvements more sign in logs - top 50 dialog table location info new location formatter for table data status.errorCode translator --- .../CippCards/CippBannerListCard.jsx | 26 ++-- .../CippComponents/CippLocationDialog.jsx | 52 ++++++++ .../CippTable/util-columnsFromAPI.js | 9 +- src/data/signinErrorCodes.json | 17 +++ .../administration/users/user/index.jsx | 111 +++++++++++++++++- src/utils/get-cipp-formatting.js | 10 ++ .../get-cipp-signin-errorcode-translation.js | 9 ++ 7 files changed, 217 insertions(+), 17 deletions(-) create mode 100644 src/components/CippComponents/CippLocationDialog.jsx create mode 100644 src/data/signinErrorCodes.json create mode 100644 src/utils/get-cipp-signin-errorcode-translation.js diff --git a/src/components/CippCards/CippBannerListCard.jsx b/src/components/CippCards/CippBannerListCard.jsx index a0bab7612a02..aa7fc42a8bca 100644 --- a/src/components/CippCards/CippBannerListCard.jsx +++ b/src/components/CippCards/CippBannerListCard.jsx @@ -16,7 +16,7 @@ import { CippPropertyListCard } from "./CippPropertyListCard"; import { CippDataTable } from "../CippTable/CippDataTable"; export const CippBannerListCard = (props) => { - const { items = [], isCollapsible = false, isFetching = false, ...other } = props; + const { items = [], isCollapsible = false, isFetching = false, children, ...other } = props; const [expanded, setExpanded] = useState(null); const handleExpand = useCallback((itemId) => { @@ -145,14 +145,18 @@ export const CippBannerListCard = (props) => { {isCollapsible && ( - {item?.propertyItems?.length > 0 && ( - - )} - {item?.table && } + + {item?.propertyItems?.length > 0 && ( + + )} + {item?.table && } + {item?.children && {item.children}} + {item?.actionButton && {item.actionButton}} + )} @@ -180,8 +184,12 @@ CippBannerListCard.propTypes = { subtext: PropTypes.string, statusColor: PropTypes.string, statusText: PropTypes.string, + actionButton: PropTypes.element, propertyItems: PropTypes.array, + table: PropTypes.object, + actionButton: PropTypes.element, isFetching: PropTypes.bool, + children: PropTypes.node, }) ).isRequired, isCollapsible: PropTypes.bool, diff --git a/src/components/CippComponents/CippLocationDialog.jsx b/src/components/CippComponents/CippLocationDialog.jsx new file mode 100644 index 000000000000..9970524ed681 --- /dev/null +++ b/src/components/CippComponents/CippLocationDialog.jsx @@ -0,0 +1,52 @@ +import { Button, Dialog, DialogActions, DialogContent, DialogTitle } from "@mui/material"; +import { useState } from "react"; +import dynamic from "next/dynamic"; // Import dynamic from next/dynamic +import { CippPropertyList } from "./CippPropertyList"; // Import CippPropertyList +import { LocationOn } from "@mui/icons-material"; + +const CippMap = dynamic(() => import("./CippMap"), { ssr: false }); // Dynamic import for CippMap + +export const CippLocationDialog = ({ location }) => { + const [open, setOpen] = useState(false); + + const handleOpen = () => { + setOpen(true); + }; + + const handleClose = () => { + setOpen(false); + }; + + const markers = [ + { + position: [location.geoCoordinates.latitude, location.geoCoordinates.longitude], + popup: `${location.city}, ${location.state}, ${location.countryOrRegion}`, + }, + ]; + + const properties = [ + { label: "City", value: location.city }, + { label: "State", value: location.state }, + { label: "Country/Region", value: location.countryOrRegion }, + ]; + + return ( + <> + + + Location Details + + + + + + + + + + ); +}; diff --git a/src/components/CippTable/util-columnsFromAPI.js b/src/components/CippTable/util-columnsFromAPI.js index 886a6f1d258b..e783ebe16133 100644 --- a/src/components/CippTable/util-columnsFromAPI.js +++ b/src/components/CippTable/util-columnsFromAPI.js @@ -30,12 +30,17 @@ const mergeKeys = (dataArray) => { export const utilColumnsFromAPI = (dataArray) => { const dataSample = mergeKeys(dataArray); - + const skipRecursion = ["location"]; const generateColumns = (obj, parentKey = "") => { return Object.keys(obj) .map((key) => { const accessorKey = parentKey ? `${parentKey}.${key}` : key; - if (typeof obj[key] === "object" && obj[key] !== null && !Array.isArray(obj[key])) { + if ( + typeof obj[key] === "object" && + obj[key] !== null && + !Array.isArray(obj[key]) && + !skipRecursion.includes(key) + ) { return generateColumns(obj[key], accessorKey); } diff --git a/src/data/signinErrorCodes.json b/src/data/signinErrorCodes.json new file mode 100644 index 000000000000..a4d279753b9a --- /dev/null +++ b/src/data/signinErrorCodes.json @@ -0,0 +1,17 @@ +{ + "0": "Success", + "50126": "Invalid username or password", + "70044": "The session has expired or is invalid due to sign-in frequency checks by conditional access", + "50089": "Flow token expired", + "53003": "Access has been blocked by Conditional Access policies", + "50140": "This error occurred due to 'Keep me signed in' interrupt when the user was signing-in", + "50097": "Device authentication required", + "65001": "Application X doesn't have permission to access application Y or the permission has been revoked", + "50053": "Account is locked because user tried to sign in too many times with an incorrect user ID or password", + "50020": "The user is unauthorized", + "50125": "Sign-in was interrupted due to a password reset or password registration entry", + "50074": "User did not pass the MFA challenge", + "50133": "Session is invalid due to expiration or recent password change", + "530002": "Your device is required to be compliant to access this resource", + "9001011": "Device policy contains unsupported required device state" +} \ No newline at end of file diff --git a/src/pages/identity/administration/users/user/index.jsx b/src/pages/identity/administration/users/user/index.jsx index 37f3d396924f..984371102e4f 100644 --- a/src/pages/identity/administration/users/user/index.jsx +++ b/src/pages/identity/administration/users/user/index.jsx @@ -11,22 +11,68 @@ import { CippCopyToClipBoard } from "../../../../../components/CippComponents/Ci import { Box, Stack } from "@mui/system"; import Grid from "@mui/material/Grid2"; import { CippUserInfoCard } from "../../../../../components/CippCards/CippUserInfoCard"; -import { Typography } from "@mui/material"; +import { SvgIcon, Typography } from "@mui/material"; import { CippBannerListCard } from "../../../../../components/CippCards/CippBannerListCard"; import { CippTimeAgo } from "../../../../../components/CippComponents/CippTimeAgo"; import { useEffect, useState } from "react"; -import { usePopover } from "../../../../../hooks/use-popover"; -import { useDialog } from "../../../../../hooks/use-dialog"; import CippUserActions from "/src/components/CippComponents/CippUserActions"; -import { PencilIcon } from "@heroicons/react/24/outline"; +import { EyeIcon, PencilIcon } from "@heroicons/react/24/outline"; +import { CippDataTable } from "/src/components/CippTable/CippDataTable"; +import dynamic from "next/dynamic"; +const CippMap = dynamic(() => import("/src/components/CippComponents/CippMap"), { ssr: false }); + +import { Button, Dialog, DialogTitle, DialogContent, IconButton } from "@mui/material"; +import { Close } from "@mui/icons-material"; +import { CippPropertyList } from "../../../../../components/CippComponents/CippPropertyList"; + +const SignInLogsDialog = ({ open, onClose, userId, tenantFilter }) => { + return ( + + + Sign-In Logs + + + + + + + + + ); +}; const Page = () => { - const popover = usePopover(); - const createDialog = useDialog(); const userSettingsDefaults = useSettings(); const router = useRouter(); const { userId } = router.query; const [waiting, setWaiting] = useState(false); + const [signInLogsDialogOpen, setSignInLogsDialogOpen] = useState(false); + useEffect(() => { if (userId) { setWaiting(true); @@ -112,6 +158,20 @@ const Page = () => { subtext: `Logged into application ${signInData.resourceDisplayName || "Unknown Application"}`, statusColor: signInData.status.errorCode === 0 ? "success.main" : "error.main", statusText: signInData.status.errorCode === 0 ? "Success" : "Failed", + actionButton: ( + + ), propertyItems: [ { label: "Client App Used", @@ -131,6 +191,39 @@ const Page = () => { value: signInData.status?.additionalDetails || "N/A", }, ], + children: ( + <> + {signInData?.location && ( + <> + Location + + + + + + + + + + )} + + ), }; // Prepare the conditional access policies items @@ -459,6 +552,12 @@ const Page = () => { )} + setSignInLogsDialogOpen(false)} + userId={userId} + tenantFilter={userSettingsDefaults.currentTenant} + /> ); }; diff --git a/src/utils/get-cipp-formatting.js b/src/utils/get-cipp-formatting.js index d8e7ff471c6d..a11dea40b087 100644 --- a/src/utils/get-cipp-formatting.js +++ b/src/utils/get-cipp-formatting.js @@ -13,11 +13,13 @@ import { CippCopyToClipBoard } from "../components/CippComponents/CippCopyToClip import { getCippLicenseTranslation } from "./get-cipp-license-translation"; import CippDataTableButton from "../components/CippTable/CippDataTableButton"; import { LinearProgressWithLabel } from "../components/linearProgressWithLabel"; +import { CippLocationDialog } from "../components/CippComponents/CippLocationDialog"; import { isoDuration, en } from "@musement/iso-duration"; import { CippTimeAgo } from "../components/CippComponents/CippTimeAgo"; import { getCippRoleTranslation } from "./get-cipp-role-translation"; import { CogIcon, ServerIcon, UserIcon, UsersIcon } from "@heroicons/react/24/outline"; import { getCippTranslation } from "./get-cipp-translation"; +import { getSignInErrorCodeTranslation } from "./get-cipp-signin-errorcode-translation"; export const getCippFormatting = (data, cellName, type, canReceive) => { const isText = type === "text"; @@ -353,6 +355,14 @@ export const getCippFormatting = (data, cellName, type, canReceive) => { ); } + if (cellName === "status.errorCode") { + return getSignInErrorCodeTranslation(data); + } + + if (cellName === "location") { + return isText ? JSON.stringify(data) : ; + } + const translateProps = ["riskLevel", "riskState", "riskDetail", "enrollmentType", "profileType"]; if (translateProps.includes(cellName)) { diff --git a/src/utils/get-cipp-signin-errorcode-translation.js b/src/utils/get-cipp-signin-errorcode-translation.js new file mode 100644 index 000000000000..10f0c257a4d1 --- /dev/null +++ b/src/utils/get-cipp-signin-errorcode-translation.js @@ -0,0 +1,9 @@ +import SignInErrorCodes from "../data/SignInErrorCodes.json"; + +export const getSignInErrorCodeTranslation = (errorCode) => { + if (SignInErrorCodes.hasOwnProperty(String(errorCode))) { + return SignInErrorCodes[String(errorCode)]; + } else { + return errorCode; + } +}; From c1bf7fe5d210a8ba271845a49f86ad42217f7642 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 24 Jan 2025 19:46:03 -0500 Subject: [PATCH 75/98] mailbox rules table --- .../administration/users/user/exchange.jsx | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/src/pages/identity/administration/users/user/exchange.jsx b/src/pages/identity/administration/users/user/exchange.jsx index a9d325f8f35a..bef693434d29 100644 --- a/src/pages/identity/administration/users/user/exchange.jsx +++ b/src/pages/identity/administration/users/user/exchange.jsx @@ -18,6 +18,10 @@ import CippExchangeSettingsForm from "../../../../../components/CippFormPages/Ci import { useForm } from "react-hook-form"; import { Alert, Button, Collapse, CircularProgress, Typography } from "@mui/material"; import { CippApiResults } from "../../../../../components/CippComponents/CippApiResults"; +import { TrashIcon } from "@heroicons/react/24/outline"; +import { CippPropertyListCard } from "../../../../../components/CippCards/CippPropertyListCard"; +import { getCippTranslation } from "../../../../../utils/get-cipp-translation"; +import { getCippFormatting } from "../../../../../utils/get-cipp-formatting"; const Page = () => { const userSettingsDefaults = useSettings(); @@ -55,6 +59,12 @@ const Page = () => { waiting: waiting, }); + const mailboxRulesRequest = ApiGetCall({ + url: `/api/ListUserMailboxRules?UserId=${userId}&tenantFilter=${userSettingsDefaults.currentTenant}`, + queryKey: `MailboxRules-${userId}`, + waiting: waiting, + }); + useEffect(() => { if (oooRequest.isSuccess) { formControl.setValue("ooo.ExternalMessage", oooRequest.data?.ExternalMessage); @@ -156,6 +166,79 @@ const Page = () => { })) || [], }, ]; + + const mailboxRuleActions = [ + { + label: "Remove Mailbox Rule", + type: "GET", + icon: , + url: "/api/ExecRemoveMailboxRule", + data: { ruleId: "Identity", userPrincipalName: "UserPrincipalName" }, + confirmText: "Are you sure you want to remove this mailbox rule?", + multiPost: false, + }, + ]; + + const mailboxRulesCard = [ + { + id: 1, + cardLabelBox: { + cardLabelBoxHeader: mailboxRulesRequest.isFetching ? ( + + ) : mailboxRulesRequest.data?.length !== 0 ? ( + + ) : ( + + ), + }, + text: "Current Mailbox Rules", + subtext: mailboxRulesRequest.data?.length + ? "Mailbox rules are configured for this user" + : "No mailbox rules configured for this user", + statusColor: "green.main", + table: { + title: "Mailbox Rules", + hideTitle: true, + data: mailboxRulesRequest.data || [], + simpleColumns: [ + "Enabled", + "Name", + "Description", + "Redirect To", + "Copy To Folder", + "Move To Folder", + "Soft Delete Message", + "Delete Message", + ], + actions: mailboxRuleActions, + offCanvas: { + children: (data) => { + const keys = Object.keys(data).filter( + (key) => !key.includes("@odata") && !key.includes("@data") + ); + const properties = []; + keys.forEach((key) => { + if (data[key] && data[key].length > 0) { + properties.push({ + label: getCippTranslation(key), + value: getCippFormatting(data[key], key), + }); + } + }); + return ( + + ); + }, + }, + }, + }, + ]; + return ( { items={calCard} isCollapsible={calPermissions.data?.length !== 0} /> + Date: Sat, 25 Jan 2025 00:15:28 -0500 Subject: [PATCH 76/98] mailbox rules tweaks --- .../administration/mailbox-rules/index.js | 6 ++--- .../administration/users/user/exchange.jsx | 24 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/pages/email/administration/mailbox-rules/index.js b/src/pages/email/administration/mailbox-rules/index.js index 84a5f8523d98..c80b749aad07 100644 --- a/src/pages/email/administration/mailbox-rules/index.js +++ b/src/pages/email/administration/mailbox-rules/index.js @@ -7,14 +7,13 @@ import { CippPropertyListCard } from "../../../../components/CippCards/CippPrope const Page = () => { const pageTitle = "Mailbox Rules"; - const simpleColumns = ["Name", "Priority", "Enabled", "UserPrincipalName", "From"]; const actions = [ { label: "Remove Mailbox Rule", type: "GET", icon: , url: "/api/ExecRemoveMailboxRule", - data: { ruleId: "Identity", userPrincipalName: "UserPrincipalName" }, + data: { ruleId: "Identity", userPrincipalName: "UserPrincipalName", ruleName: "Name" }, confirmText: "Are you sure you want to remove this mailbox rule?", multiPost: false, }, @@ -48,7 +47,8 @@ const Page = () => { diff --git a/src/pages/identity/administration/users/user/exchange.jsx b/src/pages/identity/administration/users/user/exchange.jsx index bef693434d29..e9fc7cf44a5d 100644 --- a/src/pages/identity/administration/users/user/exchange.jsx +++ b/src/pages/identity/administration/users/user/exchange.jsx @@ -173,9 +173,14 @@ const Page = () => { type: "GET", icon: , url: "/api/ExecRemoveMailboxRule", - data: { ruleId: "Identity", userPrincipalName: "UserPrincipalName" }, + data: { + ruleId: "Identity", + ruleName: "Name", + userPrincipalName: graphUserRequest.data?.[0]?.userPrincipalName, + }, confirmText: "Are you sure you want to remove this mailbox rule?", multiPost: false, + relatedQueryKeys: `MailboxRules-${userId}`, }, ]; @@ -200,16 +205,9 @@ const Page = () => { title: "Mailbox Rules", hideTitle: true, data: mailboxRulesRequest.data || [], - simpleColumns: [ - "Enabled", - "Name", - "Description", - "Redirect To", - "Copy To Folder", - "Move To Folder", - "Soft Delete Message", - "Delete Message", - ], + refreshFunction: () => mailboxRulesRequest.refetch(), + isFetching: mailboxRulesRequest.isFetching, + simpleColumns: ["Enabled", "Name", "Description", "Priority"], actions: mailboxRuleActions, offCanvas: { children: (data) => { @@ -281,7 +279,9 @@ const Page = () => { )} - {!userRequest?.data?.[0]?.Mailbox?.[0]?.error && ( + {!userRequest?.data?.[0]?.Mailbox?.[0]?.error?.includes( + "Microsoft.Exchange.Configuration.Tasks.ManagementObjectNotFoundException" + ) && ( <> From 06188127d2de07717fcc2579c7315ba87b9cf6ab Mon Sep 17 00:00:00 2001 From: John Duprey Date: Sat, 25 Jan 2025 10:38:27 -0500 Subject: [PATCH 77/98] remove deploy now action --- src/pages/endpoint/applications/queue/index.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/pages/endpoint/applications/queue/index.js b/src/pages/endpoint/applications/queue/index.js index 7ed456ad97d5..0f30341cefcb 100644 --- a/src/pages/endpoint/applications/queue/index.js +++ b/src/pages/endpoint/applications/queue/index.js @@ -5,22 +5,16 @@ import { Button } from "@mui/material"; import Link from "next/link"; import { ApiPostCall } from "../../../../api/ApiCall"; import { CippApiResults } from "../../../../components/CippComponents/CippApiResults"; +import { TrashIcon } from "@heroicons/react/24/outline"; const Page = () => { const pageTitle = "Queued Applications"; const actions = [ - { - label: "Deploy now", - type: "POST", - url: "/api/ExecAppUpload", - confirmText: - "Deploy all queued applications to tenants?\n\nNote: This job runs automatically every 12 hours.", - multiPost: false, - }, { label: "Delete Application", type: "POST", + icon: , url: "/api/RemoveQueuedApp", data: { ID: "id" }, confirmText: "Do you want to delete the queued application?", From 0f7800e0b06f15975d5cf0cfd5fcc294fcb5a93f Mon Sep 17 00:00:00 2001 From: John Duprey Date: Sat, 25 Jan 2025 10:57:01 -0500 Subject: [PATCH 78/98] location tweaks --- src/components/CippComponents/CippLocationDialog.jsx | 2 +- src/components/CippTable/util-columnsFromAPI.js | 5 +++-- src/utils/get-cipp-formatting.js | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/components/CippComponents/CippLocationDialog.jsx b/src/components/CippComponents/CippLocationDialog.jsx index 9970524ed681..403f15891395 100644 --- a/src/components/CippComponents/CippLocationDialog.jsx +++ b/src/components/CippComponents/CippLocationDialog.jsx @@ -33,7 +33,7 @@ export const CippLocationDialog = ({ location }) => { return ( <> Location Details diff --git a/src/components/CippTable/util-columnsFromAPI.js b/src/components/CippTable/util-columnsFromAPI.js index e783ebe16133..5c0ee7dfa8c1 100644 --- a/src/components/CippTable/util-columnsFromAPI.js +++ b/src/components/CippTable/util-columnsFromAPI.js @@ -2,12 +2,13 @@ import { getCippFilterVariant } from "../../utils/get-cipp-filter-variant"; import { getCippFormatting } from "../../utils/get-cipp-formatting"; import { getCippTranslation } from "../../utils/get-cipp-translation"; +const skipRecursion = ["location"]; // Function to merge keys from all objects in the array const mergeKeys = (dataArray) => { return dataArray.reduce((acc, item) => { const mergeRecursive = (obj, base = {}) => { Object.keys(obj).forEach((key) => { - if (typeof obj[key] === "object" && obj[key] !== null && !Array.isArray(obj[key])) { + if (typeof obj[key] === "object" && obj[key] !== null && !Array.isArray(obj[key]) && !skipRecursion.includes(key)) { if (typeof base[key] === "boolean") { // Skip merging if base[key] is a boolean return; @@ -30,7 +31,7 @@ const mergeKeys = (dataArray) => { export const utilColumnsFromAPI = (dataArray) => { const dataSample = mergeKeys(dataArray); - const skipRecursion = ["location"]; + const generateColumns = (obj, parentKey = "") => { return Object.keys(obj) .map((key) => { diff --git a/src/utils/get-cipp-formatting.js b/src/utils/get-cipp-formatting.js index a11dea40b087..f258b14c146b 100644 --- a/src/utils/get-cipp-formatting.js +++ b/src/utils/get-cipp-formatting.js @@ -359,7 +359,7 @@ export const getCippFormatting = (data, cellName, type, canReceive) => { return getSignInErrorCodeTranslation(data); } - if (cellName === "location") { + if (cellName === "location" && data?.geoCoordinates) { return isText ? JSON.stringify(data) : ; } From 80a8c4b463f1372d30358d0591fb2e6b022f00eb Mon Sep 17 00:00:00 2001 From: John Duprey Date: Sat, 25 Jan 2025 11:14:29 -0500 Subject: [PATCH 79/98] add external directory object id fixes linking to edit permissions and research compromised account --- src/pages/email/administration/mailboxes/index.js | 4 ++-- src/pages/identity/administration/users/user/exchange.jsx | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pages/email/administration/mailboxes/index.js b/src/pages/email/administration/mailboxes/index.js index d468d4fef10e..7407c979374b 100644 --- a/src/pages/email/administration/mailboxes/index.js +++ b/src/pages/email/administration/mailboxes/index.js @@ -22,13 +22,13 @@ const Page = () => { const actions = [ { label: "Edit permissions", - link: "/identity/administration/users/user/exchange?userId=[Id]", + link: "/identity/administration/users/user/exchange?userId=[ExternalDirectoryObjectId]", color: "info", icon: , }, { label: "Research Compromised Account", - link: "/identity/administration/users/user/bec?userId=[UPN]", + link: "/identity/administration/users/user/bec?userId=[ExternalDirectoryObjectId]", color: "info", icon: , }, diff --git a/src/pages/identity/administration/users/user/exchange.jsx b/src/pages/identity/administration/users/user/exchange.jsx index e9fc7cf44a5d..29082e3b6fb8 100644 --- a/src/pages/identity/administration/users/user/exchange.jsx +++ b/src/pages/identity/administration/users/user/exchange.jsx @@ -242,11 +242,11 @@ const Page = () => { tabOptions={tabOptions} title={title} subtitle={subtitle} - isFetching={userRequest.isLoading} + isFetching={graphUserRequest.isLoading} > - {userRequest.isLoading && } - {userRequest.isSuccess && ( + {graphUserRequest.isLoading && } + {graphUserRequest.isSuccess && ( Date: Sat, 25 Jan 2025 11:18:43 -0500 Subject: [PATCH 80/98] Update index.js --- src/pages/email/administration/mailboxes/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/email/administration/mailboxes/index.js b/src/pages/email/administration/mailboxes/index.js index 7407c979374b..81726a1abd14 100644 --- a/src/pages/email/administration/mailboxes/index.js +++ b/src/pages/email/administration/mailboxes/index.js @@ -37,7 +37,7 @@ const Page = () => { type: "GET", url: "/api/ExecSendPush", data: { - UserEmail: "mail", + UserEmail: "UPN", }, confirmText: "Are you sure you want to send an MFA request?", icon: , From 58ec93068a6dfdc2228a05ed6be955335c447d6d Mon Sep 17 00:00:00 2001 From: John Duprey Date: Sat, 25 Jan 2025 12:10:44 -0500 Subject: [PATCH 81/98] switch delete action to custom function --- src/pages/cipp/advanced/table-maintenance.js | 290 ++++++++++--------- 1 file changed, 156 insertions(+), 134 deletions(-) diff --git a/src/pages/cipp/advanced/table-maintenance.js b/src/pages/cipp/advanced/table-maintenance.js index e1bc22b0f029..849bed69bb43 100644 --- a/src/pages/cipp/advanced/table-maintenance.js +++ b/src/pages/cipp/advanced/table-maintenance.js @@ -28,6 +28,7 @@ import { useForm, useWatch } from "react-hook-form"; import { CippApiDialog } from "../../../components/CippComponents/CippApiDialog"; import { Grid } from "@mui/system"; import CippButtonCard from "../../../components/CippCards/CippButtonCard"; +import { CippApiResults } from "../../../components/CippComponents/CippApiResults"; const CustomAddEditRowDialog = ({ formControl, open, onClose, onSubmit, defaultValues }) => { const fields = useWatch({ control: formControl.control, name: "fields" }); @@ -322,155 +323,176 @@ const Page = () => { {selectedTable && ( - { - var properties = getSelectedProps(data); - setTableFilterParams({ ...data, Property: properties }); - handleRefresh(); - setAccordionExpanded(false); - })} - > - Apply Filters - - } - > - - - ({ - label: field?.label, - value: field?.name, - }))} - /> - + + { + var properties = getSelectedProps(data); + setTableFilterParams({ ...data, Property: properties }); + handleRefresh(); + setAccordionExpanded(false); + })} + > + Apply Filters + + } + > + ({ + label: field?.label, + value: field?.name, + }))} /> + + + + - - - - + + + } + actions={[ + { + label: "Edit", + type: "POST", + icon: ( + + + + ), + customFunction: (row) => { setDefaultAddEditValues({ - fields: getTableFields().map((field) => ({ - name: field?.name, - value: "", - type: field?.type, - })), + fields: Object.keys(row) + .filter((key) => key !== "ETag" && key !== "Timestamp") + .map((key) => { + const value = row[key]; + let type = "textField"; + if (typeof value === "number") { + type = "number"; + } else if (typeof value === "boolean") { + type = "switch"; + } + return { name: key, value: value, type: type }; + }), }); addEditRowDialog.handleOpen(); - }} // Open add/edit row dialog - startIcon={ - - - - } - > - Add Row - - - - } - actions={[ - { - label: "Edit", - type: "POST", - icon: ( - - - - ), - customFunction: (row) => { - setDefaultAddEditValues({ - fields: Object.keys(row) - .filter((key) => key !== "ETag" && key !== "Timestamp") - .map((key) => { - const value = row[key]; - let type = "textField"; - if (typeof value === "number") { - type = "number"; - } else if (typeof value === "boolean") { - type = "switch"; - } - return { name: key, value: value, type: type }; - }), - }); - addEditRowDialog.handleOpen(); - }, - noConfirm: true, - hideBulk: true, - }, - { - label: "Delete", - type: "POST", - icon: ( - - - - ), - url: apiUrl, - data: { - FunctionName: "Remove-AzDataTableEntity", - TableName: `!${selectedTable}`, - Parameters: { - Entity: { RowKey: "RowKey", PartitionKey: "PartitionKey", ETag: "ETag" }, + ), + url: apiUrl, + customFunction: (row) => { + var entity = []; + if (Array.isArray(row)) { + entity = row.map((r) => ({ + RowKey: r.RowKey, + PartitionKey: r.PartitionKey, + ETag: r.ETag, + })); + } else { + entity = { + RowKey: row.RowKey, + PartitionKey: row.PartitionKey, + ETag: row.ETag, + }; + } + tableRowAction.mutate({ + url: apiUrl, + data: { + FunctionName: "Remove-AzDataTableEntity", + TableName: selectedTable, + Parameters: { + Entity: entity, + }, + }, + }); }, + confirmText: + "Do you want to delete the selected row(s)? This action cannot be undone.", + multiPost: true, }, - onSuccess: handleRefresh, - confirmText: "Do you want to delete this row?", - multiPost: false, - }, - ]} - /> + ]} + /> + )} From fa819d97af52a01cc337b6cd762b03fd406462b5 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Sat, 25 Jan 2025 12:32:56 -0500 Subject: [PATCH 82/98] fix workflow --- .github/workflows/PR_Branch_Check.yml | 9 +++++---- src/utils/get-cipp-signin-errorcode-translation.js | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/PR_Branch_Check.yml b/.github/workflows/PR_Branch_Check.yml index 2fd5b8e65249..06c2f6774a39 100644 --- a/.github/workflows/PR_Branch_Check.yml +++ b/.github/workflows/PR_Branch_Check.yml @@ -16,13 +16,14 @@ permissions: jobs: check-branch: + if: github.event.repository.fork == false runs-on: ubuntu-latest steps: - name: Check and Comment on PR # Only process fork PRs with specific branch conditions # Must be a fork AND (source is main/master OR target is main/master) if: | - github.event.pull_request.head.repo.fork == true && + github.event.pull_request.head.repo.fork == true && ((github.event.pull_request.head.ref == 'main' || github.event.pull_request.head.ref == 'master') || (github.event.pull_request.base.ref == 'main' || github.event.pull_request.base.ref == 'master')) uses: actions/github-script@v7 @@ -40,20 +41,20 @@ jobs: } // Check if PR is from a fork's main/master branch - if (context.payload.pull_request.head.repo.fork && + if (context.payload.pull_request.head.repo.fork && (context.payload.pull_request.head.ref === 'main' || context.payload.pull_request.head.ref === 'master')) { message += '⚠️ This PR cannot be merged because it originates from your fork\'s main/master branch. If you are attempting to contribute code please PR from your dev branch or another non-main/master branch.\n\n'; } message += '🔒 This PR will now be automatically closed due to the above violation(s).'; - + // Post the comment await github.rest.issues.createComment({ ...context.repo, issue_number: context.issue.number, body: message }); - + // Close the PR await github.rest.pulls.update({ ...context.repo, diff --git a/src/utils/get-cipp-signin-errorcode-translation.js b/src/utils/get-cipp-signin-errorcode-translation.js index 10f0c257a4d1..a2735d65257d 100644 --- a/src/utils/get-cipp-signin-errorcode-translation.js +++ b/src/utils/get-cipp-signin-errorcode-translation.js @@ -1,4 +1,4 @@ -import SignInErrorCodes from "../data/SignInErrorCodes.json"; +import SignInErrorCodes from "/src/data/SignInErrorCodes.json"; export const getSignInErrorCodeTranslation = (errorCode) => { if (SignInErrorCodes.hasOwnProperty(String(errorCode))) { From 837a9c89d4c069c27530c03601255b86a6c93d69 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Sat, 25 Jan 2025 13:36:45 -0500 Subject: [PATCH 83/98] table tweaks --- .../CippTable/CIPPTableToptoolbar.js | 27 ++++++++++++++++--- src/components/CippTable/CippDataTable.js | 1 + .../get-cipp-signin-errorcode-translation.js | 2 +- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/components/CippTable/CIPPTableToptoolbar.js b/src/components/CippTable/CIPPTableToptoolbar.js index bbb28ffe3249..12619a983c68 100644 --- a/src/components/CippTable/CIPPTableToptoolbar.js +++ b/src/components/CippTable/CIPPTableToptoolbar.js @@ -73,7 +73,10 @@ export const CIPPTableToptoolbar = ({ //useEffect to set the column visibility to the preferred columns if they exist useEffect(() => { - if (settings?.columnDefaults?.[pageName]) { + if ( + settings?.columnDefaults?.[pageName] && + Object.keys(settings?.columnDefaults?.[pageName]).length > 0 + ) { setColumnVisibility(settings?.columnDefaults?.[pageName]); } }, [settings?.columnDefaults?.[pageName], router, usedColumns]); @@ -88,27 +91,42 @@ export const CIPPTableToptoolbar = ({ }); const resetToDefaultVisibility = () => { - setColumnVisibility({}); + setColumnVisibility((prevVisibility) => { + const updatedVisibility = {}; + for (const col in prevVisibility) { + if (Array.isArray(originalSimpleColumns)) { + updatedVisibility[col] = originalSimpleColumns.includes(col); + } + } + return updatedVisibility; + }); settings.handleUpdate({ columnDefaults: { ...settings?.columnDefaults, [pageName]: {}, }, }); + columnPopover.handleClose(); }; const resetToPreferedVisibility = () => { - if (settings?.columnDefaults?.[pageName]) { + if ( + settings?.columnDefaults?.[pageName] && + Object.keys(settings?.columnDefaults?.[pageName]).length > 0 + ) { setColumnVisibility(settings?.columnDefaults?.[pageName]); } else { setColumnVisibility((prevVisibility) => { const updatedVisibility = {}; for (const col in prevVisibility) { - updatedVisibility[col] = originalSimpleColumns.includes(col); + if (Array.isArray(originalSimpleColumns)) { + updatedVisibility[col] = originalSimpleColumns.includes(col); + } } return updatedVisibility; }); } + columnPopover.handleClose(); }; const saveAsPreferedColumns = () => { @@ -118,6 +136,7 @@ export const CIPPTableToptoolbar = ({ [pageName]: columnVisibility, }, }); + columnPopover.handleClose(); }; const mergeCaseInsensitive = (obj1, obj2) => { diff --git a/src/components/CippTable/CippDataTable.js b/src/components/CippTable/CippDataTable.js index 6d59e6f372d6..914c0d7675c5 100644 --- a/src/components/CippTable/CippDataTable.js +++ b/src/components/CippTable/CippDataTable.js @@ -248,6 +248,7 @@ export const CippDataTable = (props) => { table={table} api={api} queryKey={queryKey} + simpleColumns={simpleColumns} data={data} columnVisibility={columnVisibility} getRequestData={getRequestData} diff --git a/src/utils/get-cipp-signin-errorcode-translation.js b/src/utils/get-cipp-signin-errorcode-translation.js index a2735d65257d..2f7b7de4e752 100644 --- a/src/utils/get-cipp-signin-errorcode-translation.js +++ b/src/utils/get-cipp-signin-errorcode-translation.js @@ -1,4 +1,4 @@ -import SignInErrorCodes from "/src/data/SignInErrorCodes.json"; +import SignInErrorCodes from "/src/data/SignInErrorCodes"; export const getSignInErrorCodeTranslation = (errorCode) => { if (SignInErrorCodes.hasOwnProperty(String(errorCode))) { From 601b59d2075351f38eb9e8b4db148f44c50d4277 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Sat, 25 Jan 2025 13:48:25 -0500 Subject: [PATCH 84/98] Update get-cipp-signin-errorcode-translation.js --- src/utils/get-cipp-signin-errorcode-translation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/get-cipp-signin-errorcode-translation.js b/src/utils/get-cipp-signin-errorcode-translation.js index 2f7b7de4e752..cd65ff68004d 100644 --- a/src/utils/get-cipp-signin-errorcode-translation.js +++ b/src/utils/get-cipp-signin-errorcode-translation.js @@ -1,4 +1,4 @@ -import SignInErrorCodes from "/src/data/SignInErrorCodes"; +import SignInErrorCodes from "/src/data/signInErrorCodes"; export const getSignInErrorCodeTranslation = (errorCode) => { if (SignInErrorCodes.hasOwnProperty(String(errorCode))) { From 51f5d50acf0eeae4531aa9e1cbca98aeb17032f3 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Sat, 25 Jan 2025 14:19:39 -0500 Subject: [PATCH 85/98] Update get-cipp-signin-errorcode-translation.js --- src/utils/get-cipp-signin-errorcode-translation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/get-cipp-signin-errorcode-translation.js b/src/utils/get-cipp-signin-errorcode-translation.js index cd65ff68004d..00f0074e05f5 100644 --- a/src/utils/get-cipp-signin-errorcode-translation.js +++ b/src/utils/get-cipp-signin-errorcode-translation.js @@ -1,4 +1,4 @@ -import SignInErrorCodes from "/src/data/signInErrorCodes"; +import SignInErrorCodes from "/src/data/signinErrorCodes"; export const getSignInErrorCodeTranslation = (errorCode) => { if (SignInErrorCodes.hasOwnProperty(String(errorCode))) { From 3f33d27625af718e5e55873cc92562ad11c75ba2 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Sat, 25 Jan 2025 20:43:29 +0100 Subject: [PATCH 86/98] fixes formatting offboarding wizarad --- src/utils/get-cipp-formatting.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/utils/get-cipp-formatting.js b/src/utils/get-cipp-formatting.js index d8e7ff471c6d..937bce69c4e6 100644 --- a/src/utils/get-cipp-formatting.js +++ b/src/utils/get-cipp-formatting.js @@ -232,9 +232,13 @@ export const getCippFormatting = (data, cellName, type, canReceive) => { } if (data?.enabled === true && data?.date) { - return isText - ? `Yes, Scheduled for ${new Date(data.date).toLocaleString()}` - : `Yes, Scheduled for ${new Date(data.date).toLocaleString()}`; + return isText ? ( + `Yes, Scheduled for ${new Date(data.date).toLocaleString()}` + ) : ( + <> + Yes, Scheduled for + + ); } if (data?.enabled === true || data?.enabled === false) { return isText ? ( From ac5a319ab7306ee20011167665bd419aa34b01a0 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Sat, 25 Jan 2025 14:45:04 -0500 Subject: [PATCH 87/98] fix inputvalue --- src/pages/tenant/administration/alert-configuration/alert.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/tenant/administration/alert-configuration/alert.jsx b/src/pages/tenant/administration/alert-configuration/alert.jsx index b5ab6871e70b..510c9c598b27 100644 --- a/src/pages/tenant/administration/alert-configuration/alert.jsx +++ b/src/pages/tenant/administration/alert-configuration/alert.jsx @@ -209,7 +209,7 @@ const AlertWizard = () => { const getInputParams = () => { if (values.command.value.requiresInput) { return { - [values.command.value.inputName]: values[values.command.value.inputName], + InputValue: values[values.command.value.inputName], }; } return {}; From ed0d2a7f55d957bb3f2a6931c05af4abfd2d3188 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Sat, 25 Jan 2025 15:33:29 -0500 Subject: [PATCH 88/98] Update dev_deploy.yml --- .github/workflows/dev_deploy.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dev_deploy.yml b/.github/workflows/dev_deploy.yml index 5a8c8dfe933d..01fd002acea0 100644 --- a/.github/workflows/dev_deploy.yml +++ b/.github/workflows/dev_deploy.yml @@ -18,7 +18,7 @@ jobs: id: builddeploy uses: Azure/static-web-apps-deploy@v1 with: - azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_RW }} # change this to your repository secret name + azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_AMBITIOUS_MOSS_0A047A40F }} # change this to your repository secret name repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments) action: 'upload' ###### Repository/Build Configurations - These values can be configured to match your app requirements. ###### @@ -37,5 +37,5 @@ jobs: id: closepullrequest uses: Azure/static-web-apps-deploy@v1 with: - azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_RW }} # change this to your repository secret name + azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_AMBITIOUS_MOSS_0A047A40F }} # change this to your repository secret name action: 'close' From eaa936d106c37945b7afcb9eb7297756a0e5ef12 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Sat, 25 Jan 2025 19:48:04 -0500 Subject: [PATCH 89/98] Update preferences.js --- src/pages/cipp/preferences.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/pages/cipp/preferences.js b/src/pages/cipp/preferences.js index 6cc782d44508..c73c67d2c52c 100644 --- a/src/pages/cipp/preferences.js +++ b/src/pages/cipp/preferences.js @@ -14,6 +14,24 @@ const Page = () => { const settings = useSettings(); const formcontrol = useForm({ mode: "onChange", defaultValues: settings }); + // retrieve StaticWebAppsAuthCookie from browser cookies + const cookieString = document.cookie + .split("; ") + .find((row) => row.startsWith("StaticWebAppsAuthCookie=")); + const StaticWebAppsAuthCookie = cookieString.split("=")[1]; + // base64 decode the cookie and log + console.log(atob(StaticWebAppsAuthCookie)); + + // retrieve the expiration date of the cookie + const expires = cookieString.split(";").find((part) => part.trim().startsWith("expires=")); + if (expires) { + const expirationDate = new Date(expires.split("=")[1]); + console.log("Cookie expiration date:", expirationDate); + } else { + console.log("No expiration date found for the cookie."); + } + + const addedAttributes = [ { value: "consentProvidedForMinor", label: "consentProvidedForMinor" }, { value: "employeeId", label: "employeeId" }, From ea636c04e48a17088837d36cf2e9eb1b41e49b84 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Sat, 25 Jan 2025 21:52:22 -0500 Subject: [PATCH 90/98] Update preferences.js --- src/pages/cipp/preferences.js | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/pages/cipp/preferences.js b/src/pages/cipp/preferences.js index c73c67d2c52c..6cc782d44508 100644 --- a/src/pages/cipp/preferences.js +++ b/src/pages/cipp/preferences.js @@ -14,24 +14,6 @@ const Page = () => { const settings = useSettings(); const formcontrol = useForm({ mode: "onChange", defaultValues: settings }); - // retrieve StaticWebAppsAuthCookie from browser cookies - const cookieString = document.cookie - .split("; ") - .find((row) => row.startsWith("StaticWebAppsAuthCookie=")); - const StaticWebAppsAuthCookie = cookieString.split("=")[1]; - // base64 decode the cookie and log - console.log(atob(StaticWebAppsAuthCookie)); - - // retrieve the expiration date of the cookie - const expires = cookieString.split(";").find((part) => part.trim().startsWith("expires=")); - if (expires) { - const expirationDate = new Date(expires.split("=")[1]); - console.log("Cookie expiration date:", expirationDate); - } else { - console.log("No expiration date found for the cookie."); - } - - const addedAttributes = [ { value: "consentProvidedForMinor", label: "consentProvidedForMinor" }, { value: "employeeId", label: "employeeId" }, From 2035d560bf90b6eb667fec1c352cb1df08767fbe Mon Sep 17 00:00:00 2001 From: John Duprey Date: Sun, 26 Jan 2025 00:23:53 -0500 Subject: [PATCH 91/98] fix issue on mailbox rules page --- src/components/CippTable/util-columnsFromAPI.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/components/CippTable/util-columnsFromAPI.js b/src/components/CippTable/util-columnsFromAPI.js index 5c0ee7dfa8c1..c5084c988110 100644 --- a/src/components/CippTable/util-columnsFromAPI.js +++ b/src/components/CippTable/util-columnsFromAPI.js @@ -8,12 +8,21 @@ const mergeKeys = (dataArray) => { return dataArray.reduce((acc, item) => { const mergeRecursive = (obj, base = {}) => { Object.keys(obj).forEach((key) => { - if (typeof obj[key] === "object" && obj[key] !== null && !Array.isArray(obj[key]) && !skipRecursion.includes(key)) { + if ( + typeof obj[key] === "object" && + obj[key] !== null && + !Array.isArray(obj[key]) && + !skipRecursion.includes(key) + ) { if (typeof base[key] === "boolean") { // Skip merging if base[key] is a boolean return; } - base[key] = mergeRecursive(obj[key], base[key] || {}); + if (typeof base[key] !== "object" || Array.isArray(base[key])) { + // Re-initialize base[key] if it's not an object + base[key] = {}; + } + base[key] = mergeRecursive(obj[key], base[key]); } else if (typeof obj[key] === "boolean") { base[key] = obj[key]; } else if (typeof obj[key] === "string" && obj[key].toUpperCase() === "FAILED") { From f18b61e2b1d3222b14e41791b0bd572a44f17dbb Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Sun, 26 Jan 2025 18:04:12 +0100 Subject: [PATCH 92/98] add edit template --- .../endpoint/MEM/list-templates/index.js | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/pages/endpoint/MEM/list-templates/index.js b/src/pages/endpoint/MEM/list-templates/index.js index 75e54a4c88bb..e61d3930aedc 100644 --- a/src/pages/endpoint/MEM/list-templates/index.js +++ b/src/pages/endpoint/MEM/list-templates/index.js @@ -1,12 +1,35 @@ import { Layout as DashboardLayout } from "/src/layouts/index.js"; import { CippTablePage } from "/src/components/CippComponents/CippTablePage.jsx"; -import { EyeIcon, TrashIcon } from "@heroicons/react/24/outline"; +import { EyeIcon, PencilIcon, TrashIcon } from "@heroicons/react/24/outline"; import CippJsonView from "../../../../components/CippFormPages/CippJSONView"; const Page = () => { const pageTitle = "Available Endpoint Manager Templates"; const actions = [ + { + label: "Edit Template Name and Description", + type: "POST", + url: "/api/ExecEditTemplate", + fields: [ + { + type: "textField", + name: "displayName", + label: "Display Name", + }, + { + type: "textField", + name: "description", + label: "Description", + }, + ], + data: { GUID: "GUID", Type: "!IntuneTemplate" }, + confirmText: + "Enter the new name and description for the template. Warning: This will disconnect the template from a template library if applied.", + multiPost: false, + icon: , + color: "info", + }, { label: "Delete Template", type: "GET", From 2a144e44b0f3f907d5e64689c9cb8a4d129d4e92 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Sun, 26 Jan 2025 19:47:31 +0100 Subject: [PATCH 93/98] fixed all tenants --- src/components/CippFormPages/CippSchedulerForm.jsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/CippFormPages/CippSchedulerForm.jsx b/src/components/CippFormPages/CippSchedulerForm.jsx index 3049dba77660..e18e9c57421a 100644 --- a/src/components/CippFormPages/CippSchedulerForm.jsx +++ b/src/components/CippFormPages/CippSchedulerForm.jsx @@ -120,7 +120,12 @@ const CippSchedulerForm = (props) => { )} - + From 079cf5269ff644e7f24d54416cf40b3c6e41a4d6 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Sun, 26 Jan 2025 20:22:42 +0100 Subject: [PATCH 94/98] up version --- public/version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/version.json b/public/version.json index dba87fa5d4c8..b2e85d7083a9 100644 --- a/public/version.json +++ b/public/version.json @@ -1,3 +1,3 @@ { - "version": "7.0.5" + "version": "7.1.0" } From f11ec8a2cc807e986003146fcbc025b44a9c3185 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Sun, 26 Jan 2025 20:31:02 +0100 Subject: [PATCH 95/98] Delete .github/workflows/PR_Branch_Check.yml --- .github/workflows/PR_Branch_Check.yml | 63 --------------------------- 1 file changed, 63 deletions(-) delete mode 100644 .github/workflows/PR_Branch_Check.yml diff --git a/.github/workflows/PR_Branch_Check.yml b/.github/workflows/PR_Branch_Check.yml deleted file mode 100644 index 06c2f6774a39..000000000000 --- a/.github/workflows/PR_Branch_Check.yml +++ /dev/null @@ -1,63 +0,0 @@ -name: PR Branch Check - -on: - # Using pull_request_target instead of pull_request for secure handling of fork PRs - pull_request_target: - # Only run on these PR events - types: [opened, synchronize, reopened] - # Only check PRs targeting these branches - branches: - - main - - master - -permissions: - pull-requests: write - issues: write - -jobs: - check-branch: - if: github.event.repository.fork == false - runs-on: ubuntu-latest - steps: - - name: Check and Comment on PR - # Only process fork PRs with specific branch conditions - # Must be a fork AND (source is main/master OR target is main/master) - if: | - github.event.pull_request.head.repo.fork == true && - ((github.event.pull_request.head.ref == 'main' || github.event.pull_request.head.ref == 'master') || - (github.event.pull_request.base.ref == 'main' || github.event.pull_request.base.ref == 'master')) - uses: actions/github-script@v7 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - let message = ''; - - message += '🔄 If you are attempting to update your CIPP repo please follow the instructions at: https://docs.cipp.app/setup/self-hosting-guide/updating '; - message += '\n\n'; - - // Check if PR is targeting main/master - if (context.payload.pull_request.base.ref === 'main' || context.payload.pull_request.base.ref === 'master') { - message += '⚠️ PRs cannot target the main branch directly. If you are attempting to contribute code please PR to the dev branch.\n\n'; - } - - // Check if PR is from a fork's main/master branch - if (context.payload.pull_request.head.repo.fork && - (context.payload.pull_request.head.ref === 'main' || context.payload.pull_request.head.ref === 'master')) { - message += '⚠️ This PR cannot be merged because it originates from your fork\'s main/master branch. If you are attempting to contribute code please PR from your dev branch or another non-main/master branch.\n\n'; - } - - message += '🔒 This PR will now be automatically closed due to the above violation(s).'; - - // Post the comment - await github.rest.issues.createComment({ - ...context.repo, - issue_number: context.issue.number, - body: message - }); - - // Close the PR - await github.rest.pulls.update({ - ...context.repo, - pull_number: context.issue.number, - state: 'closed' - }); From 1d499e86731a58648c1222cdaa48e5e5547458f1 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Sun, 26 Jan 2025 20:31:41 +0100 Subject: [PATCH 96/98] Delete .github/workflows/dev_deploy.yml --- .github/workflows/dev_deploy.yml | 41 -------------------------------- 1 file changed, 41 deletions(-) delete mode 100644 .github/workflows/dev_deploy.yml diff --git a/.github/workflows/dev_deploy.yml b/.github/workflows/dev_deploy.yml deleted file mode 100644 index 01fd002acea0..000000000000 --- a/.github/workflows/dev_deploy.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: CIPP Development Frontend CI/CD - -on: - push: - branches: - - dev - -jobs: - build_and_deploy_job: - if: github.event.repository.fork == false && github.event_name == 'push' - runs-on: ubuntu-latest - name: Build and Deploy Job - steps: - - uses: actions/checkout@v4 - with: - submodules: true - - name: Build And Deploy - id: builddeploy - uses: Azure/static-web-apps-deploy@v1 - with: - azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_AMBITIOUS_MOSS_0A047A40F }} # change this to your repository secret name - repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments) - action: 'upload' - ###### Repository/Build Configurations - These values can be configured to match your app requirements. ###### - # For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig - app_location: '/' # App source code path - api_location: '' # Api source code path - optional - output_location: 'out' # Built app content directory - optional - ###### End of Repository/Build Configurations ###### - - close_pull_request_job: - if: github.event.repository.fork == false && github.event_name == 'pull_request' && github.event.action == 'closed' - runs-on: ubuntu-latest - name: Close Pull Request Job - steps: - - name: Close Pull Request - id: closepullrequest - uses: Azure/static-web-apps-deploy@v1 - with: - azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_AMBITIOUS_MOSS_0A047A40F }} # change this to your repository secret name - action: 'close' From e6c37bf8c629d5ad54170cfdf9b45c4b0ff8446d Mon Sep 17 00:00:00 2001 From: John Duprey Date: Sun, 26 Jan 2025 16:45:04 -0500 Subject: [PATCH 97/98] tenant selector fix --- .../CippIntegrationTenantMapping.jsx | 61 ++++++++++--------- .../CippSettings/CippCustomRoles.jsx | 8 ++- src/pages/cipp/super-admin/sam-app-roles.js | 5 +- 3 files changed, 40 insertions(+), 34 deletions(-) diff --git a/src/components/CippIntegrations/CippIntegrationTenantMapping.jsx b/src/components/CippIntegrations/CippIntegrationTenantMapping.jsx index 4461db5a8bc9..8f1ecc67ec5c 100644 --- a/src/components/CippIntegrations/CippIntegrationTenantMapping.jsx +++ b/src/components/CippIntegrations/CippIntegrationTenantMapping.jsx @@ -22,6 +22,7 @@ import { CippFormTenantSelector } from "../CippComponents/CippFormTenantSelector import { Sync, SyncAlt } from "@mui/icons-material"; import { CippFormComponent } from "../CippComponents/CippFormComponent"; import { CippApiResults } from "../CippComponents/CippApiResults"; +import { ApiGetCallWithPagination } from "../../api/ApiCall"; const CippIntegrationSettings = ({ children }) => { const router = useRouter(); @@ -35,7 +36,7 @@ const CippIntegrationSettings = ({ children }) => { queryKey: `IntegrationTenantMapping-${router.query.id}`, }); - const tenantList = ApiGetCall({ + const tenantList = ApiGetCallWithPagination({ url: "/api/ListTenants", data: { AllTenantSelector: false }, queryKey: "ListTenants-notAllTenants", @@ -94,37 +95,37 @@ const CippIntegrationSettings = ({ children }) => { }; const handleAutoMap = () => { - const newTableData = []; - tenantList.data.forEach((tenant) => { - const matchingCompany = mappings.data.Companies.find( - (company) => company.name === tenant.displayName - ); - if ( - Array.isArray(tableData) && - tableData?.find((item) => item.TenantId === tenant.customerId) - ) - return; - if (matchingCompany) { - newTableData.push({ - TenantId: tenant.customerId, - Tenant: tenant.displayName, - IntegrationName: matchingCompany.name, - IntegrationId: matchingCompany.value, + const newTableData = []; + tenantList.data?.pages[0]?.forEach((tenant) => { + const matchingCompany = mappings.data.Companies.find( + (company) => company.name === tenant.displayName + ); + if ( + Array.isArray(tableData) && + tableData?.find((item) => item.TenantId === tenant.customerId) + ) + return; + if (matchingCompany) { + newTableData.push({ + TenantId: tenant.customerId, + Tenant: tenant.displayName, + IntegrationName: matchingCompany.name, + IntegrationId: matchingCompany.value, + }); + } + }); + if (Array.isArray(tableData)) { + setTableData([...tableData, ...newTableData]); + } else { + setTableData(newTableData); + } + if (extension.autoMapSyncApi) { + automapPostCall.mutate({ + url: `/api/ExecExtensionMapping?AutoMapping=${router.query.id}`, + queryKey: `IntegrationTenantMapping-${router.query.id}`, }); } - }); - if (Array.isArray(tableData)) { - setTableData([...tableData, ...newTableData]); - } else { - setTableData(newTableData); - } - if (extension.autoMapSyncApi) { - automapPostCall.mutate({ - url: `/api/ExecExtensionMapping?AutoMapping=${router.query.id}`, - queryKey: `IntegrationTenantMapping-${router.query.id}`, - }); - } - }; + }; const actions = [ { diff --git a/src/components/CippSettings/CippCustomRoles.jsx b/src/components/CippSettings/CippCustomRoles.jsx index 2f8ddd517dcc..6387ae339a4f 100644 --- a/src/components/CippSettings/CippCustomRoles.jsx +++ b/src/components/CippSettings/CippCustomRoles.jsx @@ -14,7 +14,7 @@ import { } from "@mui/material"; import Grid from "@mui/material/Grid2"; -import { ApiGetCall, ApiPostCall } from "../../api/ApiCall"; +import { ApiGetCall, ApiGetCallWithPagination, ApiPostCall } from "../../api/ApiCall"; import { CippOffCanvas } from "/src/components/CippComponents/CippOffCanvas"; import { CippFormTenantSelector } from "/src/components/CippComponents/CippFormTenantSelector"; import { Save } from "@mui/icons-material"; @@ -67,10 +67,14 @@ export const CippCustomRoles = () => { queryKey: "customRoleList", }); - const { data: tenants = [], isSuccess: tenantsSuccess } = ApiGetCall({ + const { + data: { pages = [] } = {}, + isSuccess: tenantsSuccess, + } = ApiGetCallWithPagination({ url: "/api/ListTenants?AllTenantSelector=true", queryKey: "ListTenants-AllTenantSelector", }); + const tenants = pages[0] || []; useEffect(() => { if (customRoleListSuccess && tenantsSuccess && selectedRole !== currentRole?.value) { diff --git a/src/pages/cipp/super-admin/sam-app-roles.js b/src/pages/cipp/super-admin/sam-app-roles.js index f038c60f8249..a5c0632206eb 100644 --- a/src/pages/cipp/super-admin/sam-app-roles.js +++ b/src/pages/cipp/super-admin/sam-app-roles.js @@ -5,7 +5,7 @@ import CippFormPage from "/src/components/CippFormPages/CippFormPage"; import { Alert, CardContent, Stack, Typography } from "@mui/material"; import { WarningAmberOutlined } from "@mui/icons-material"; import { useForm } from "react-hook-form"; -import { ApiGetCall } from "../../../api/ApiCall"; +import { ApiGetCall, ApiGetCallWithPagination } from "../../../api/ApiCall"; import { useEffect } from "react"; import CippFormComponent from "../../../components/CippComponents/CippFormComponent"; import GDAPRoles from "/src/data/GDAPRoles"; @@ -23,10 +23,11 @@ const Page = () => { queryKey: "ExecSAMRoles", }); - const { data: tenants = [], isSuccess: tenantsSuccess } = ApiGetCall({ + const { data: tenantsData = { pages: [] }, isSuccess: tenantsSuccess } = ApiGetCallWithPagination({ url: "/api/ListTenants?AllTenantSelector=true", queryKey: "ListTenants-AllTenantSelector", }); + const tenants = tenantsData?.pages?.[0] || []; useEffect(() => { if (execSAMRoles.isSuccess && tenantsSuccess) { From a3346a9e510eeab824030e3bb72eb53888c04dd8 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Sun, 26 Jan 2025 17:00:36 -0500 Subject: [PATCH 98/98] up version --- public/version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/version.json b/public/version.json index b2e85d7083a9..fa68337146a1 100644 --- a/public/version.json +++ b/public/version.json @@ -1,3 +1,3 @@ { - "version": "7.1.0" + "version": "7.1.1" }