From 163f6eff24078922ddac8f48037c611fcd5973b3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Mar 2026 23:44:10 +0000 Subject: [PATCH 01/29] chore(deps): bump react-virtuoso from 4.18.1 to 4.18.3 Bumps [react-virtuoso](https://github.com/petyosi/react-virtuoso/tree/HEAD/packages/react-virtuoso) from 4.18.1 to 4.18.3. - [Release notes](https://github.com/petyosi/react-virtuoso/releases) - [Changelog](https://github.com/petyosi/react-virtuoso/blob/master/packages/react-virtuoso/CHANGELOG.md) - [Commits](https://github.com/petyosi/react-virtuoso/commits/react-virtuoso@4.18.3/packages/react-virtuoso) --- updated-dependencies: - dependency-name: react-virtuoso dependency-version: 4.18.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 05a3468c52e4..a9c5f67a039b 100644 --- a/package.json +++ b/package.json @@ -98,7 +98,7 @@ "react-redux": "9.2.0", "react-syntax-highlighter": "^16.1.0", "react-time-ago": "^7.3.3", - "react-virtuoso": "^4.18.1", + "react-virtuoso": "^4.18.3", "react-window": "^2.2.5", "recharts": "^3.7.0", "redux": "5.0.1", diff --git a/yarn.lock b/yarn.lock index 605fa06ca1d0..23b69f0013a9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6898,10 +6898,10 @@ react-virtualized-auto-sizer@^1.0.26: resolved "https://registry.yarnpkg.com/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.26.tgz#e9470ef6a778dc4f1d5fd76305fa2d8b610c357a" integrity sha512-CblNyiNVw2o+hsa5/49NH2ogGxZ+t+3aweRvNSq7TVjDIlwk7ir4lencEg5HxHeSzwNarSkNkiu0qJSOXtxm5A== -react-virtuoso@^4.18.1: - version "4.18.1" - resolved "https://registry.yarnpkg.com/react-virtuoso/-/react-virtuoso-4.18.1.tgz#3eb7078f2739a31b96c723374019e587deeb6ebc" - integrity sha512-KF474cDwaSb9+SJ380xruBB4P+yGWcVkcu26HtMqYNMTYlYbrNy8vqMkE+GpAApPPufJqgOLMoWMFG/3pJMXUA== +react-virtuoso@^4.18.3: + version "4.18.3" + resolved "https://registry.yarnpkg.com/react-virtuoso/-/react-virtuoso-4.18.3.tgz#12e69600c258bc6e6bd31c2516942ef08700deac" + integrity sha512-fLz/peHAx4Eu0DLHurFEEI7Y6n5CqEoxBh04rgJM9yMuOJah2a9zWg/MUOmZLcp7zuWYorXq5+5bf3IRgkNvWg== react-window@^2.2.5: version "2.2.5" From 81a29758c589a3a491e0e57bd9c99112c4be8701 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Mar 2026 23:44:28 +0000 Subject: [PATCH 02/29] chore(deps): bump i18next from 25.8.13 to 25.8.18 Bumps [i18next](https://github.com/i18next/i18next) from 25.8.13 to 25.8.18. - [Release notes](https://github.com/i18next/i18next/releases) - [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md) - [Commits](https://github.com/i18next/i18next/compare/v25.8.13...v25.8.18) --- updated-dependencies: - dependency-name: i18next dependency-version: 25.8.18 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 05a3468c52e4..1624b2d984d8 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "export-to-csv": "^1.3.0", "formik": "2.4.9", "gray-matter": "4.0.3", - "i18next": "25.8.13", + "i18next": "25.8.18", "javascript-time-ago": "^2.6.2", "jspdf": "^4.2.0", "jspdf-autotable": "^5.0.7", diff --git a/yarn.lock b/yarn.lock index 605fa06ca1d0..5a786969fab5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4898,12 +4898,12 @@ hyphen@^1.6.4: resolved "https://registry.yarnpkg.com/hyphen/-/hyphen-1.10.6.tgz#0e779d280e696102b97d7e42f5ca5de2cc97e274" integrity sha512-fXHXcGFTXOvZTSkPJuGOQf5Lv5T/R2itiiCVPg9LxAje5D00O0pP83yJShFq5V89Ly//Gt6acj7z8pbBr34stw== -i18next@25.8.13: - version "25.8.13" - resolved "https://registry.yarnpkg.com/i18next/-/i18next-25.8.13.tgz#1f9df59329f1706f02b2b58b5d1f75196ddb6e4a" - integrity sha512-E0vzjBY1yM+nsFrtgkjLhST2NBkirkvOVoQa0MSldhsuZ3jUge7ZNpuwG0Cfc74zwo5ZwRzg3uOgT+McBn32iA== +i18next@25.8.18: + version "25.8.18" + resolved "https://registry.yarnpkg.com/i18next/-/i18next-25.8.18.tgz#51863b65bc42e3525271f2680ebbf7d150ff53cc" + integrity sha512-lzY5X83BiL5AP77+9DydbrqkQHFN9hUzWGjqjLpPcp5ZOzuu1aSoKaU3xbBLSjWx9dAzW431y+d+aogxOZaKRA== dependencies: - "@babel/runtime" "^7.28.4" + "@babel/runtime" "^7.28.6" ignore@^5.2.0: version "5.3.2" From 5832129586d5391118b51aab00f21a405a7ccb0c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Mar 2026 23:44:39 +0000 Subject: [PATCH 03/29] chore(deps): bump typescript from 5.9.2 to 5.9.3 Bumps [typescript](https://github.com/microsoft/TypeScript) from 5.9.2 to 5.9.3. - [Release notes](https://github.com/microsoft/TypeScript/releases) - [Commits](https://github.com/microsoft/TypeScript/compare/v5.9.2...v5.9.3) --- updated-dependencies: - dependency-name: typescript dependency-version: 5.9.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 05a3468c52e4..6a9829560027 100644 --- a/package.json +++ b/package.json @@ -110,7 +110,7 @@ "simplebar": "6.3.3", "simplebar-react": "3.3.2", "stylis-plugin-rtl": "2.1.1", - "typescript": "5.9.2", + "typescript": "5.9.3", "yup": "1.7.1" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index 605fa06ca1d0..b4dbd2c187b7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7805,10 +7805,10 @@ typescript-eslint@^8.46.0: "@typescript-eslint/typescript-estree" "8.56.0" "@typescript-eslint/utils" "8.56.0" -typescript@5.9.2: - version "5.9.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.2.tgz#d93450cddec5154a2d5cabe3b8102b83316fb2a6" - integrity sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A== +typescript@5.9.3: + version "5.9.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f" + integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw== uc.micro@^2.0.0, uc.micro@^2.1.0: version "2.1.0" From 21bb15a988eb7fe98316202f1d437b898001aff6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Mar 2026 23:45:00 +0000 Subject: [PATCH 04/29] chore(deps): bump @tiptap/starter-kit from 3.20.0 to 3.20.1 Bumps [@tiptap/starter-kit](https://github.com/ueberdosis/tiptap/tree/HEAD/packages/starter-kit) from 3.20.0 to 3.20.1. - [Release notes](https://github.com/ueberdosis/tiptap/releases) - [Changelog](https://github.com/ueberdosis/tiptap/blob/develop/packages/starter-kit/CHANGELOG.md) - [Commits](https://github.com/ueberdosis/tiptap/commits/v3.20.1/packages/starter-kit) --- updated-dependencies: - dependency-name: "@tiptap/starter-kit" dependency-version: 3.20.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 250 +++++++++++++++++++++++++-------------------------- 2 files changed, 126 insertions(+), 126 deletions(-) diff --git a/package.json b/package.json index 05a3468c52e4..384ce8836838 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "@tiptap/extension-table": "^3.19.0", "@tiptap/pm": "^3.4.1", "@tiptap/react": "^3.4.1", - "@tiptap/starter-kit": "^3.20.0", + "@tiptap/starter-kit": "^3.20.1", "@uiw/react-json-view": "^2.0.0-alpha.41", "@vvo/tzdb": "^6.198.0", "apexcharts": "5.3.5", diff --git a/yarn.lock b/yarn.lock index 605fa06ca1d0..937f41caca83 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2255,20 +2255,20 @@ resolved "https://registry.yarnpkg.com/@tanstack/virtual-core/-/virtual-core-3.11.2.tgz#00409e743ac4eea9afe5b7708594d5fcebb00212" integrity sha512-vTtpNt7mKCiZ1pwU9hfKPhpdVO2sVzFQsxoVBGtOSHxlrRRzYr8iQ2TlwbAcRYCcEiZ9ECAM8kBzH0v2+VzfKw== -"@tiptap/core@^3.20.0", "@tiptap/core@^3.4.1": - version "3.20.0" - resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-3.20.0.tgz#dac72894d83829f2fbbabee2e90a748d7c1479ee" - integrity sha512-aC9aROgia/SpJqhsXFiX9TsligL8d+oeoI8W3u00WI45s0VfsqjgeKQLDLF7Tu7hC+7F02teC84SAHuup003VQ== +"@tiptap/core@^3.20.1", "@tiptap/core@^3.4.1": + version "3.20.1" + resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-3.20.1.tgz#3e870175541144cc9ca292c804609f7c43549a8b" + integrity sha512-SwkPEWIfaDEZjC8SEIi4kZjqIYUbRgLUHUuQezo5GbphUNC8kM1pi3C3EtoOPtxXrEbY6e4pWEzW54Pcrd+rVA== -"@tiptap/extension-blockquote@^3.20.0": - version "3.20.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-blockquote/-/extension-blockquote-3.20.0.tgz#92e4a8ed00cf4fcab056766b848fc0a551847e5b" - integrity sha512-LQzn6aGtL4WXz2+rYshl/7/VnP2qJTpD7fWL96GXAzhqviPEY1bJES7poqJb3MU/gzl8VJUVzVzU1VoVfUKlbA== +"@tiptap/extension-blockquote@^3.20.1": + version "3.20.1" + resolved "https://registry.yarnpkg.com/@tiptap/extension-blockquote/-/extension-blockquote-3.20.1.tgz#7991cbe4250f4389c80fc06adc499ad1e65cb7bf" + integrity sha512-WzNXk/63PQI2fav4Ta6P0GmYRyu8Gap1pV3VUqaVK829iJ6Zt1T21xayATHEHWMK27VT1GLPJkx9Ycr2jfDyQw== -"@tiptap/extension-bold@^3.20.0": - version "3.20.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-bold/-/extension-bold-3.20.0.tgz#4298d24cb6c7759f6233eec5e24e9fd7c7efdf38" - integrity sha512-sQklEWiyf58yDjiHtm5vmkVjfIc/cBuSusmCsQ0q9vGYnEF1iOHKhGpvnCeEXNeqF3fiJQRlquzt/6ymle3Iwg== +"@tiptap/extension-bold@^3.20.1": + version "3.20.1" + resolved "https://registry.yarnpkg.com/@tiptap/extension-bold/-/extension-bold-3.20.1.tgz#c5dda7450cb1d575ee3f53fafd02fb0169151df6" + integrity sha512-fz++Qv6Rk/Hov0IYG/r7TJ1Y4zWkuGONe0UN5g0KY32NIMg3HeOHicbi4xsNWTm9uAOl3eawWDkezEMrleObMw== "@tiptap/extension-bubble-menu@^3.13.0": version "3.13.0" @@ -2277,127 +2277,127 @@ dependencies: "@floating-ui/dom" "^1.0.0" -"@tiptap/extension-bullet-list@^3.20.0": - version "3.20.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-bullet-list/-/extension-bullet-list-3.20.0.tgz#57bcba5988990f39cb71c7901173da9b4979523b" - integrity sha512-OcKMeopBbqWzhSi6o8nNz0aayogg1sfOAhto3NxJu3Ya32dwBFqmHXSYM6uW4jOphNvVPyjiq9aNRh3qTdd1dw== +"@tiptap/extension-bullet-list@^3.20.1": + version "3.20.1" + resolved "https://registry.yarnpkg.com/@tiptap/extension-bullet-list/-/extension-bullet-list-3.20.1.tgz#7184db6e65533904d65d403690784f5fea508208" + integrity sha512-mbrlvOZo5OF3vLhp+3fk9KuL/6J/wsN0QxF6ZFRAHzQ9NkJdtdfARcBeBnkWXGN8inB6YxbTGY1/E4lmBkOpOw== -"@tiptap/extension-code-block@^3.20.0": - version "3.20.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block/-/extension-code-block-3.20.0.tgz#5c00e8ae017c32ff4dd629447635a25fcb9d0f52" - integrity sha512-lBbmNek14aCjrHcBcq3PRqWfNLvC6bcRa2Osc6e/LtmXlcpype4f6n+Yx+WZ+f2uUh0UmDRCz7BEyUETEsDmlQ== +"@tiptap/extension-code-block@^3.20.1": + version "3.20.1" + resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block/-/extension-code-block-3.20.1.tgz#c4b69ff6eb0929700cfd70aedee0754960efb17d" + integrity sha512-vKejwBq+Nlj4Ybd3qOyDxIQKzYymdNH+8eXkKwGShk2nfLJIxq69DCyGvmuHgipIO1qcYPJ149UNpGN+YGcdmA== -"@tiptap/extension-code@^3.20.0": - version "3.20.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-code/-/extension-code-3.20.0.tgz#6aa18bc21ed8a3a6a899653c48add1cecc64783d" - integrity sha512-TYDWFeSQ9umiyrqsT6VecbuhL8XIHkUhO+gEk0sVvH67ZLwjFDhAIIgWIr1/dbIGPcvMZM19E7xUUhAdIaXaOQ== +"@tiptap/extension-code@^3.20.1": + version "3.20.1" + resolved "https://registry.yarnpkg.com/@tiptap/extension-code/-/extension-code-3.20.1.tgz#611b95bb1b583fdca53b39d3abd46165c7fe3721" + integrity sha512-509DHINIA/Gg+eTG7TEkfsS8RUiPLH5xZNyLRT0A1oaoaJmECKfrV6aAm05IdfTyqDqz6LW5pbnX6DdUC4keug== -"@tiptap/extension-document@^3.20.0": - version "3.20.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-document/-/extension-document-3.20.0.tgz#e10f92139c97354ab917f3095d4011d6703528f6" - integrity sha512-oJfLIG3vAtZo/wg29WiBcyWt22KUgddpP8wqtCE+kY5Dw8znLR9ehNmVWlSWJA5OJUMO0ntAHx4bBT+I2MBd5w== +"@tiptap/extension-document@^3.20.1": + version "3.20.1" + resolved "https://registry.yarnpkg.com/@tiptap/extension-document/-/extension-document-3.20.1.tgz#b8cc670096ad755f3c0daa1f5297aa7536947ff0" + integrity sha512-9vrqdGmRV7bQCSY3NLgu7UhIwgOCDp4sKqMNsoNRX0aZ021QQMTvBQDPkiRkCf7MNsnWrNNnr52PVnULEn3vFQ== -"@tiptap/extension-dropcursor@^3.20.0": - version "3.20.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-dropcursor/-/extension-dropcursor-3.20.0.tgz#459d6c5d7e5f4dc1152246901387e000596fbb40" - integrity sha512-d+cxplRlktVgZPwatnc34IArlppM0IFKS1J5wLk+ba1jidizsbMVh45tP/BTK2flhyfRqcNoB5R0TArhUpbkNQ== +"@tiptap/extension-dropcursor@^3.20.1": + version "3.20.1" + resolved "https://registry.yarnpkg.com/@tiptap/extension-dropcursor/-/extension-dropcursor-3.20.1.tgz#c2e3f8536164258c7532da32bb132c082c8e625d" + integrity sha512-K18L9FX4znn+ViPSIbTLOGcIaXMx/gLNwAPE8wPLwswbHhQqdiY1zzdBw6drgOc1Hicvebo2dIoUlSXOZsOEcw== "@tiptap/extension-floating-menu@^3.13.0": version "3.13.0" resolved "https://registry.yarnpkg.com/@tiptap/extension-floating-menu/-/extension-floating-menu-3.13.0.tgz#03d03292add49d1b380cdb1ff3890b2956d4e3f5" integrity sha512-OsezV2cMofZM4c13gvgi93IEYBUzZgnu8BXTYZQiQYekz4bX4uulBmLa1KOA9EN71FzS+SoLkXHU0YzlbLjlxA== -"@tiptap/extension-gapcursor@^3.20.0": - version "3.20.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-gapcursor/-/extension-gapcursor-3.20.0.tgz#df89dd19417c020c6e9529e2db5077d08353e4a1" - integrity sha512-P/LasfvG9/qFq43ZAlNbAnPnXC+/RJf49buTrhtFvI9Zg0+Lbpjx1oh6oMHB19T88Y28KtrckfFZ8aTSUWDq6w== +"@tiptap/extension-gapcursor@^3.20.1": + version "3.20.1" + resolved "https://registry.yarnpkg.com/@tiptap/extension-gapcursor/-/extension-gapcursor-3.20.1.tgz#b1ba47085c3a9deb4d6a899c85ac358b33e37772" + integrity sha512-kZOtttV6Ai8VUAgEng3h4WKFbtdSNJ6ps7r0cRPY+FctWhVmgNb/JJwwyC+vSilR7nRENAhrA/Cv/RxVlvLw+g== -"@tiptap/extension-hard-break@^3.20.0": - version "3.20.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-hard-break/-/extension-hard-break-3.20.0.tgz#fff1553d3b41ad32d3979d618f0f894cee926f46" - integrity sha512-rqvhMOw4f+XQmEthncbvDjgLH6fz8L9splnKZC7OeS0eX8b0qd7+xI1u5kyxF3KA2Z0BnigES++jjWuecqV6mA== +"@tiptap/extension-hard-break@^3.20.1": + version "3.20.1" + resolved "https://registry.yarnpkg.com/@tiptap/extension-hard-break/-/extension-hard-break-3.20.1.tgz#5cb4e0892f05f4658d948831afcc3c6121147c77" + integrity sha512-9sKpmg/IIdlLXimYWUZ3PplIRcehv4Oc7V1miTqlnAthMzjMqigDkjjgte4JZV67RdnDJTQkRw8TklCAU28Emg== -"@tiptap/extension-heading@^3.20.0", "@tiptap/extension-heading@^3.4.1": - version "3.20.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-heading/-/extension-heading-3.20.0.tgz#a16dbb625d91556399912fa110b04b2ad2dfd2e3" - integrity sha512-JgJhurnCe3eN6a0lEsNQM/46R1bcwzwWWZEFDSb1P9dR8+t1/5v7cMZWsSInpD7R4/74iJn0+M5hcXLwCmBmYA== +"@tiptap/extension-heading@^3.20.1", "@tiptap/extension-heading@^3.4.1": + version "3.20.1" + resolved "https://registry.yarnpkg.com/@tiptap/extension-heading/-/extension-heading-3.20.1.tgz#e626c7ad0b9906d8045372e726a9a277657f3eaa" + integrity sha512-unudyfQP6FxnyWinxvPqe/51DG91J6AaJm666RnAubgYMCgym+33kBftx4j4A6qf+ddWYbD00thMNKOnVLjAEQ== -"@tiptap/extension-horizontal-rule@^3.20.0": - version "3.20.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-3.20.0.tgz#53ffe2f9b9627f27f85b02d75878583dfb044107" - integrity sha512-6uvcutFMv+9wPZgptDkbRDjAm3YVxlibmkhWD5GuaWwS9L/yUtobpI3GycujRSUZ8D3q6Q9J7LqpmQtQRTalWA== +"@tiptap/extension-horizontal-rule@^3.20.1": + version "3.20.1" + resolved "https://registry.yarnpkg.com/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-3.20.1.tgz#a7c53eeffce44d20106e3fd84a17dd3b65f887fd" + integrity sha512-rjFKFXNntdl0jay8oIGFvvykHlpyQTLmrH3Ag2fj3i8yh6MVvqhtaDomYQbw5sxECd5hBkL+T4n2d2DRuVw/QQ== "@tiptap/extension-image@^3.4.1": version "3.13.0" resolved "https://registry.yarnpkg.com/@tiptap/extension-image/-/extension-image-3.13.0.tgz#55edb952e86c2ebed436cd53def8b2e743d71d7e" integrity sha512-223uzLUkIa1rkK7aQK3AcIXe6LbCtmnpVb7sY5OEp+LpSaSPyXwyrZ4A0EO1o98qXG68/0B2OqMntFtA9c5Fbw== -"@tiptap/extension-italic@^3.20.0": - version "3.20.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-italic/-/extension-italic-3.20.0.tgz#176c2080a75082d296797618c2ed84e9defc7ef9" - integrity sha512-/DhnKQF8yN8RxtuL8abZ28wd5281EaGoE2Oha35zXSOF1vNYnbyt8Ymkv/7u1BcWEWTvRPgaju0YCGXisPRLYw== +"@tiptap/extension-italic@^3.20.1": + version "3.20.1" + resolved "https://registry.yarnpkg.com/@tiptap/extension-italic/-/extension-italic-3.20.1.tgz#9ddd60cd41c5ad4b614909ab2cab385c886c1d4b" + integrity sha512-ZYRX13Kt8tR8JOzSXirH3pRpi8x30o7LHxZY58uXBdUvr3tFzOkh03qbN523+diidSVeHP/aMd/+IrplHRkQug== -"@tiptap/extension-link@^3.20.0": - version "3.20.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-link/-/extension-link-3.20.0.tgz#b3f2d89aabb88ed1eb66925e59fe82d1d2b866e9" - integrity sha512-qI/5A+R0ZWBxo/8HxSn1uOyr7odr3xHBZ/gzOR1GUJaZqjlJxkWFX0RtXMbLKEGEvT25o345cF7b0wFznEh8qA== +"@tiptap/extension-link@^3.20.1": + version "3.20.1" + resolved "https://registry.yarnpkg.com/@tiptap/extension-link/-/extension-link-3.20.1.tgz#e9cf33ae4a30b3ceb41a1aa7f923cce9fb5809ca" + integrity sha512-oYTTIgsQMqpkSnJAuAc+UtIKMuI4lv9e1y4LfI1iYm6NkEUHhONppU59smhxHLzb3Ww7YpDffbp5IgDTAiJztA== dependencies: linkifyjs "^4.3.2" -"@tiptap/extension-list-item@^3.20.0": - version "3.20.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-list-item/-/extension-list-item-3.20.0.tgz#60ec36a3326d3bf28982b653b63f6cab5c7d9d9f" - integrity sha512-qEtjaaGPuqaFB4VpLrGDoIe9RHnckxPfu6d3rc22ap6TAHCDyRv05CEyJogqccnFceG/v5WN4znUBER8RWnWHA== +"@tiptap/extension-list-item@^3.20.1": + version "3.20.1" + resolved "https://registry.yarnpkg.com/@tiptap/extension-list-item/-/extension-list-item-3.20.1.tgz#fb48984d8fbd776dc85d39641047d3bfc103a8dd" + integrity sha512-tzgnyTW82lYJkUnadYbatwkI9dLz/OWRSWuFpQPRje/ItmFMWuQ9c9NDD8qLbXPdEYnvrgSAA+ipCD/1G0qA0Q== -"@tiptap/extension-list-keymap@^3.20.0": - version "3.20.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-list-keymap/-/extension-list-keymap-3.20.0.tgz#3e49292b0cdf0a7b6d8f08ae5acdd7014c15cdc1" - integrity sha512-Z4GvKy04Ms4cLFN+CY6wXswd36xYsT2p/YL0V89LYFMZTerOeTjFYlndzn6svqL8NV1PRT5Diw4WTTxJSmcJPA== +"@tiptap/extension-list-keymap@^3.20.1": + version "3.20.1" + resolved "https://registry.yarnpkg.com/@tiptap/extension-list-keymap/-/extension-list-keymap-3.20.1.tgz#8737d2b11e222376b12ad5d6f063ff1cf45251e6" + integrity sha512-Dr0xsQKx0XPOgDg7xqoWwfv7FFwZ3WeF3eOjqh3rDXlNHMj1v+UW5cj1HLphrsAZHTrVTn2C+VWPJkMZrSbpvQ== -"@tiptap/extension-list@^3.20.0": - version "3.20.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-list/-/extension-list-3.20.0.tgz#17d379fe34e09a9b42ab620f7ff571826485c7d5" - integrity sha512-+V0/gsVWAv+7vcY0MAe6D52LYTIicMSHw00wz3ISZgprSb2yQhJ4+4gurOnUrQ4Du3AnRQvxPROaofwxIQ66WQ== +"@tiptap/extension-list@^3.20.1": + version "3.20.1" + resolved "https://registry.yarnpkg.com/@tiptap/extension-list/-/extension-list-3.20.1.tgz#cfbfeb8b66139598501467d9b0ec889821a79bd9" + integrity sha512-euBRAn0mkV7R2VEE+AuOt3R0j9RHEMFXamPFmtvTo8IInxDClusrm6mJoDjS8gCGAXsQCRiAe1SCQBPgGbOOwg== -"@tiptap/extension-ordered-list@^3.20.0": - version "3.20.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-ordered-list/-/extension-ordered-list-3.20.0.tgz#ec449716854d496ef7c1ea9243c2c467fa5d3cb1" - integrity sha512-jVKnJvrizLk7etwBMfyoj6H2GE4M+PD4k7Bwp6Bh1ohBWtfIA1TlngdS842Mx5i1VB2e3UWIwr8ZH46gl6cwMA== +"@tiptap/extension-ordered-list@^3.20.1": + version "3.20.1" + resolved "https://registry.yarnpkg.com/@tiptap/extension-ordered-list/-/extension-ordered-list-3.20.1.tgz#749b514ac42b1315d637d5d29c692f6e2344e428" + integrity sha512-Y+3Ad7OwAdagqdYwCnbqf7/to5ypD4NnUNHA0TXRCs7cAHRA8AdgPoIcGFpaaSpV86oosNU3yfeJouYeroffog== -"@tiptap/extension-paragraph@^3.20.0": - version "3.20.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-paragraph/-/extension-paragraph-3.20.0.tgz#b1778746021ac38894287d62d9429ac309a632ef" - integrity sha512-mM99zK4+RnEXIMCv6akfNATAs0Iija6FgyFA9J9NZ6N4o8y9QiNLLa6HjLpAC+W+VoCgQIekyoF/Q9ftxmAYDQ== +"@tiptap/extension-paragraph@^3.20.1": + version "3.20.1" + resolved "https://registry.yarnpkg.com/@tiptap/extension-paragraph/-/extension-paragraph-3.20.1.tgz#0c2ee1cdf9f3bacf713ce4db0f47abcb393c29c8" + integrity sha512-QFrAtXNyv7JSnomMQc1nx5AnG9mMznfbYJAbdOQYVdbLtAzTfiTuNPNbQrufy5ZGtGaHxDCoaygu2QEfzaKG+Q== -"@tiptap/extension-strike@^3.20.0": - version "3.20.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-strike/-/extension-strike-3.20.0.tgz#a1ec09a1a56aad98d6e7dc493d77547cad4403ee" - integrity sha512-0vcTZRRAiDfon3VM1mHBr9EFmTkkUXMhm0Xtdtn0bGe+sIqufyi+hUYTEw93EQOD9XNsPkrud6jzQNYpX2H3AQ== +"@tiptap/extension-strike@^3.20.1": + version "3.20.1" + resolved "https://registry.yarnpkg.com/@tiptap/extension-strike/-/extension-strike-3.20.1.tgz#b8110518b00aa780fecb70e206ea82a12f447319" + integrity sha512-EYgyma10lpsY+rwbVQL9u+gA7hBlKLSMFH7Zgd37FSxukOjr+HE8iKPQQ+SwbGejyDsPlLT8Z5Jnuxo5Ng90Pg== "@tiptap/extension-table@^3.19.0": version "3.19.0" resolved "https://registry.yarnpkg.com/@tiptap/extension-table/-/extension-table-3.19.0.tgz#a5f9be88e319f60dc7b8df1321f95a31b20fe991" integrity sha512-Lg8DlkkDUMYE/CcGOxoCWF98B2i7VWh+AGgqlF+XWrHjhlKHfENLRXm1a0vWuyyP3NknRYILoaaZ1s7QzmXKRA== -"@tiptap/extension-text@^3.20.0": - version "3.20.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-text/-/extension-text-3.20.0.tgz#bdf0ac6e0c638c9dbb99a5a2b5a07db2b8cba1de" - integrity sha512-tf8bE8tSaOEWabCzPm71xwiUhyMFKqY9jkP5af3Kr1/F45jzZFIQAYZooHI/+zCHRrgJ99MQHKHe1ZNvODrKHQ== +"@tiptap/extension-text@^3.20.1": + version "3.20.1" + resolved "https://registry.yarnpkg.com/@tiptap/extension-text/-/extension-text-3.20.1.tgz#f102ee6d28961f4f62a41f589d0cebfa93483c09" + integrity sha512-7PlIbYW8UenV6NPOXHmv8IcmPGlGx6HFq66RmkJAOJRPXPkTLAiX0N8rQtzUJ6jDEHqoJpaHFEHJw0xzW1yF+A== -"@tiptap/extension-underline@^3.20.0": - version "3.20.0" - resolved "https://registry.yarnpkg.com/@tiptap/extension-underline/-/extension-underline-3.20.0.tgz#535aebafc9e30da51df3be2c2065c49539535672" - integrity sha512-LzNXuy2jwR/y+ymoUqC72TiGzbOCjioIjsDu0MNYpHuHqTWPK5aV9Mh0nbZcYFy/7fPlV1q0W139EbJeYBZEAQ== +"@tiptap/extension-underline@^3.20.1": + version "3.20.1" + resolved "https://registry.yarnpkg.com/@tiptap/extension-underline/-/extension-underline-3.20.1.tgz#a07f6631a2bc5932e9872b97c22f16c6dd792611" + integrity sha512-fmHvDKzwCgnZUwRreq8tYkb1YyEwgzZ6QQkAQ0CsCRtvRMqzerr3Duz0Als4i8voZTuGDEL3VR6nAJbLAb/wPg== -"@tiptap/extensions@^3.20.0": - version "3.20.0" - resolved "https://registry.yarnpkg.com/@tiptap/extensions/-/extensions-3.20.0.tgz#22bad09a1d861446e17e0d9732439940d80ed808" - integrity sha512-HIsXX942w3nbxEQBlMAAR/aa6qiMBEP7CsSMxaxmTIVAmW35p6yUASw6GdV1u0o3lCZjXq2OSRMTskzIqi5uLg== +"@tiptap/extensions@^3.20.1": + version "3.20.1" + resolved "https://registry.yarnpkg.com/@tiptap/extensions/-/extensions-3.20.1.tgz#c95ffd378c2417cf88879ac46e78d4633d6eabe0" + integrity sha512-JRc/v+OBH0qLTdvQ7HvHWTxGJH73QOf1MC0R8NhOX2QnAbg2mPFv1h+FjGa2gfLGuCXBdWQomjekWkUKbC4e5A== -"@tiptap/pm@^3.20.0", "@tiptap/pm@^3.4.1": - version "3.20.0" - resolved "https://registry.yarnpkg.com/@tiptap/pm/-/pm-3.20.0.tgz#d9a1b92a1cb061059977952e6ec2afe8dff67857" - integrity sha512-jn+2KnQZn+b+VXr8EFOJKsnjVNaA4diAEr6FOazupMt8W8ro1hfpYtZ25JL87Kao/WbMze55sd8M8BDXLUKu1A== +"@tiptap/pm@^3.20.1", "@tiptap/pm@^3.4.1": + version "3.20.1" + resolved "https://registry.yarnpkg.com/@tiptap/pm/-/pm-3.20.1.tgz#06827f1497c3477147908e25eb91dfa1ce1c4988" + integrity sha512-6kCiGLvpES4AxcEuOhb7HR7/xIeJWMjZlb6J7e8zpiIh5BoQc7NoRdctsnmFEjZvC19bIasccshHQ7H2zchWqw== dependencies: prosemirror-changeset "^2.3.0" prosemirror-collab "^1.3.1" @@ -2430,35 +2430,35 @@ "@tiptap/extension-bubble-menu" "^3.13.0" "@tiptap/extension-floating-menu" "^3.13.0" -"@tiptap/starter-kit@^3.20.0": - version "3.20.0" - resolved "https://registry.yarnpkg.com/@tiptap/starter-kit/-/starter-kit-3.20.0.tgz#d356f15632c52f90e8eea8b5c912cab3b01b0866" - integrity sha512-W4+1re35pDNY/7rpXVg+OKo/Fa4Gfrn08Bq3E3fzlJw6gjE3tYU8dY9x9vC2rK9pd9NOp7Af11qCFDaWpohXkw== - dependencies: - "@tiptap/core" "^3.20.0" - "@tiptap/extension-blockquote" "^3.20.0" - "@tiptap/extension-bold" "^3.20.0" - "@tiptap/extension-bullet-list" "^3.20.0" - "@tiptap/extension-code" "^3.20.0" - "@tiptap/extension-code-block" "^3.20.0" - "@tiptap/extension-document" "^3.20.0" - "@tiptap/extension-dropcursor" "^3.20.0" - "@tiptap/extension-gapcursor" "^3.20.0" - "@tiptap/extension-hard-break" "^3.20.0" - "@tiptap/extension-heading" "^3.20.0" - "@tiptap/extension-horizontal-rule" "^3.20.0" - "@tiptap/extension-italic" "^3.20.0" - "@tiptap/extension-link" "^3.20.0" - "@tiptap/extension-list" "^3.20.0" - "@tiptap/extension-list-item" "^3.20.0" - "@tiptap/extension-list-keymap" "^3.20.0" - "@tiptap/extension-ordered-list" "^3.20.0" - "@tiptap/extension-paragraph" "^3.20.0" - "@tiptap/extension-strike" "^3.20.0" - "@tiptap/extension-text" "^3.20.0" - "@tiptap/extension-underline" "^3.20.0" - "@tiptap/extensions" "^3.20.0" - "@tiptap/pm" "^3.20.0" +"@tiptap/starter-kit@^3.20.1": + version "3.20.1" + resolved "https://registry.yarnpkg.com/@tiptap/starter-kit/-/starter-kit-3.20.1.tgz#a97c2d1389f05be22742b29bbe3c6cac9fe2ec70" + integrity sha512-opqWxL/4OTEiqmVC0wsU4o3JhAf6LycJ2G/gRIZVAIFLljI9uHfpPMTFGxZ5w9IVVJaP5PJysfwW/635kKqkrw== + dependencies: + "@tiptap/core" "^3.20.1" + "@tiptap/extension-blockquote" "^3.20.1" + "@tiptap/extension-bold" "^3.20.1" + "@tiptap/extension-bullet-list" "^3.20.1" + "@tiptap/extension-code" "^3.20.1" + "@tiptap/extension-code-block" "^3.20.1" + "@tiptap/extension-document" "^3.20.1" + "@tiptap/extension-dropcursor" "^3.20.1" + "@tiptap/extension-gapcursor" "^3.20.1" + "@tiptap/extension-hard-break" "^3.20.1" + "@tiptap/extension-heading" "^3.20.1" + "@tiptap/extension-horizontal-rule" "^3.20.1" + "@tiptap/extension-italic" "^3.20.1" + "@tiptap/extension-link" "^3.20.1" + "@tiptap/extension-list" "^3.20.1" + "@tiptap/extension-list-item" "^3.20.1" + "@tiptap/extension-list-keymap" "^3.20.1" + "@tiptap/extension-ordered-list" "^3.20.1" + "@tiptap/extension-paragraph" "^3.20.1" + "@tiptap/extension-strike" "^3.20.1" + "@tiptap/extension-text" "^3.20.1" + "@tiptap/extension-underline" "^3.20.1" + "@tiptap/extensions" "^3.20.1" + "@tiptap/pm" "^3.20.1" "@trysound/sax@0.2.0": version "0.2.0" From f09f11e9b1372fb5ce105ee1c6105aeca50fb7bb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Mar 2026 23:45:05 +0000 Subject: [PATCH 05/29] chore(deps): bump LanceMcCarthy/Action-AzureBlobUpload Bumps [LanceMcCarthy/Action-AzureBlobUpload](https://github.com/lancemccarthy/action-azureblobupload) from 3.8.0 to 3.9.0. - [Release notes](https://github.com/lancemccarthy/action-azureblobupload/releases) - [Commits](https://github.com/lancemccarthy/action-azureblobupload/compare/v3.8.0...v3.9.0) --- updated-dependencies: - dependency-name: LanceMcCarthy/Action-AzureBlobUpload dependency-version: 3.9.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/cipp_dev_build.yml | 2 +- .github/workflows/cipp_frontend_build.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cipp_dev_build.yml b/.github/workflows/cipp_dev_build.yml index f19a6d403a92..b2eb21ee9939 100644 --- a/.github/workflows/cipp_dev_build.yml +++ b/.github/workflows/cipp_dev_build.yml @@ -47,7 +47,7 @@ jobs: # Upload to Azure Blob Storage - name: Azure Blob Upload - uses: LanceMcCarthy/Action-AzureBlobUpload@v3.8.0 + uses: LanceMcCarthy/Action-AzureBlobUpload@v3.9.0 with: connection_string: ${{ secrets.AZURE_CONNECTION_STRING }} container_name: cipp diff --git a/.github/workflows/cipp_frontend_build.yml b/.github/workflows/cipp_frontend_build.yml index bf86646e19cd..900a1adb0992 100644 --- a/.github/workflows/cipp_frontend_build.yml +++ b/.github/workflows/cipp_frontend_build.yml @@ -47,7 +47,7 @@ jobs: # Upload to Azure Blob Storage - name: Azure Blob Upload - uses: LanceMcCarthy/Action-AzureBlobUpload@v3.8.0 + uses: LanceMcCarthy/Action-AzureBlobUpload@v3.9.0 with: connection_string: ${{ secrets.AZURE_CONNECTION_STRING }} container_name: cipp From 973abb50b0a239ed5031c913cad751d9ee32bc54 Mon Sep 17 00:00:00 2001 From: "Brad M.K." <260478956+Brad-M-K@users.noreply.github.com> Date: Thu, 12 Mar 2026 01:17:16 +0000 Subject: [PATCH 06/29] Add password configuration page with Classic and Passphrase options Replaces inline password type toggle with dedicated configuration page. Adds support for Classic passwords and Passphrase both with configurable word/character count, separator, capitalization, and optional number/special character appending. Updates CippPasswordSettings component to display current configuration and link to new settings. --- .../CippSettings/CippPasswordSettings.jsx | 125 ++++--- .../cipp/settings/password-config/index.js | 329 ++++++++++++++++++ 2 files changed, 401 insertions(+), 53 deletions(-) create mode 100644 src/pages/cipp/settings/password-config/index.js diff --git a/src/components/CippSettings/CippPasswordSettings.jsx b/src/components/CippSettings/CippPasswordSettings.jsx index 1394beff3fe5..56a4845cbb28 100644 --- a/src/components/CippSettings/CippPasswordSettings.jsx +++ b/src/components/CippSettings/CippPasswordSettings.jsx @@ -1,79 +1,98 @@ -import { Button, ButtonGroup, SvgIcon, Typography } from "@mui/material"; +import { Button, Chip, SvgIcon, Tooltip, Typography } from "@mui/material"; import CippButtonCard from "../CippCards/CippButtonCard"; -import { ApiGetCall, ApiPostCall } from "../../api/ApiCall"; -import { KeyIcon } from "@heroicons/react/24/outline"; +import { useRouter } from "next/router"; +import { Cog6ToothIcon } from "@heroicons/react/24/outline"; +import { ApiGetCall } from "../../api/ApiCall"; + +// Password configuration constants +const PASSWORD_TYPES = { + CLASSIC: 'Classic', + PASSPHRASE: 'Passphrase' +}; + +const DEFAULT_VALUES = { + CHAR_COUNT: 14, + WORD_COUNT: 4, + SPECIAL_CHAR_SET: '$%&*#', + SEPARATOR: '-' +}; const CippPasswordSettings = () => { + const router = useRouter(); const passwordSetting = ApiGetCall({ url: "/api/ExecPasswordConfig?list=true", queryKey: "PasswordSettings", }); - const passwordChange = ApiPostCall({ - datafromUrl: true, - relatedQueryKeys: "PasswordSettings", - }); + // Validate API response structure and handle loading/error states + const isValidResponse = passwordSetting.data && + passwordSetting.data.Results && + typeof passwordSetting.data.Results === 'object' && + Object.prototype.hasOwnProperty.call(passwordSetting.data.Results, 'passwordType'); + + const isLoading = passwordSetting.isLoading; + const hasError = passwordSetting.isError || (!isLoading && !isValidResponse); + + // Use defaults when data is not available + const r = isValidResponse ? passwordSetting.data.Results : null; + const isClassic = !r || r?.passwordType === PASSWORD_TYPES.CLASSIC; - const handlePasswordTypeChange = (type) => { - passwordChange.mutate({ - url: "/api/ExecPasswordConfig", - data: { passwordType: type }, - queryKey: "PasswordSettingsPost", - }); + const currentLabel = isClassic + ? `Classic — ${r?.charCount || DEFAULT_VALUES.CHAR_COUNT} characters` + : `Passphrase — ${r?.wordCount || DEFAULT_VALUES.WORD_COUNT} words`; + + const getErrorMessage = () => { + if (passwordSetting.isError) { + return "Network error loading settings. Click Configure to update."; + } + if (!isLoading && !isValidResponse) { + return "Invalid server response. Click Configure to update settings."; + } + return ""; }; - const PasswordTypeButtons = () => { - const passwordTypes = ["Classic", "Correct-Battery-Horse"]; - return passwordTypes.map((type) => ( - - )); + const handleConfigureClick = () => { + router.push("/cipp/settings/password-config"); }; + return ( - - - - - + } + onClick={handleConfigureClick} + > + Configure + + } > - Choose your password style. Classic passwords are a combination of letters and symbols. - Correct-Battery-Horse style is a passphrase, which is easier to remember and more secure - than classic passwords. + Configure password generation settings including type, length, character sets, and + passphrase options. + + {hasError && !isLoading && ( + + {getErrorMessage()} + + )} ); }; diff --git a/src/pages/cipp/settings/password-config/index.js b/src/pages/cipp/settings/password-config/index.js new file mode 100644 index 000000000000..540dd2853b2f --- /dev/null +++ b/src/pages/cipp/settings/password-config/index.js @@ -0,0 +1,329 @@ +import { useEffect, useState, useCallback } from "react"; +import { + Alert, + Box, + Button, + Card, + CardContent, + Container, + Divider, + FormControlLabel, + Stack, + SvgIcon, + Switch, + TextField, + ToggleButton, + ToggleButtonGroup, + Typography, +} from "@mui/material"; +import { Grid } from "@mui/system"; +import { Layout as DashboardLayout } from "../../../../layouts/index.js"; +import { useRouter } from "next/router"; +import { ApiGetCall, ApiPostCall } from "../../../../api/ApiCall"; +import { ArrowLeftIcon, CheckIcon } from "@heroicons/react/24/outline"; +import { CippHead } from "../../../../components/CippComponents/CippHead"; +import { CippApiResults } from "../../../../components/CippComponents/CippApiResults"; + +// Password configuration constants +const PASSWORD_TYPES = { + CLASSIC: 'Classic', + PASSPHRASE: 'Passphrase' +}; + +const DEFAULT_VALUES = { + CHAR_COUNT: 14, + WORD_COUNT: 4, + SPECIAL_CHAR_SET: '$%&*#', + SEPARATOR: '-' +}; + +function normalizeConfigForBackend(config) { + return { + passwordType: String(config.passwordType || PASSWORD_TYPES.CLASSIC), + charCount: String(parseInt(config.charCount, 10) || DEFAULT_VALUES.CHAR_COUNT), + includeUppercase: String(Boolean(config.includeUppercase)), + includeLowercase: String(Boolean(config.includeLowercase)), + includeDigits: String(Boolean(config.includeDigits)), + includeSpecialChars: String(Boolean(config.includeSpecialChars)), + specialCharSet: String(config.specialCharSet || DEFAULT_VALUES.SPECIAL_CHAR_SET), + wordCount: String(parseInt(config.wordCount, 10) || DEFAULT_VALUES.WORD_COUNT), + separator: config.separator !== undefined && config.separator !== null ? String(config.separator) : DEFAULT_VALUES.SEPARATOR, + capitalizeWords: String(Boolean(config.capitalizeWords)), + appendNumber: String(Boolean(config.appendNumber)), + appendSpecialChar: String(Boolean(config.appendSpecialChar)), + }; +} + + +const DEFAULT_CONFIG = { + passwordType: PASSWORD_TYPES.CLASSIC, + charCount: String(DEFAULT_VALUES.CHAR_COUNT), + includeUppercase: true, + includeLowercase: true, + includeDigits: true, + includeSpecialChars: true, + specialCharSet: DEFAULT_VALUES.SPECIAL_CHAR_SET, + wordCount: String(DEFAULT_VALUES.WORD_COUNT), + separator: DEFAULT_VALUES.SEPARATOR, + capitalizeWords: false, + appendNumber: false, + appendSpecialChar: false, +}; + +// ── Page ────────────────────────────────────────────────────────────────────── + +const Page = () => { + const router = useRouter(); + const [config, setConfig] = useState(DEFAULT_CONFIG); + + const passwordSetting = ApiGetCall({ url: "/api/ExecPasswordConfig?list=true", queryKey: "PasswordSettings" }); + const passwordSave = ApiPostCall({ datafromUrl: true, relatedQueryKeys: "PasswordSettings" }); + + useEffect(() => { + if (passwordSetting.isSuccess && passwordSetting.data) { + const r = passwordSetting.data.Results; + const toBool = (v, def) => { + if (v === undefined || v === null) return def; + if (typeof v === 'boolean') return v; + if (typeof v === 'string') return v.toLowerCase() === 'true'; + if (typeof v === 'number') return v === 1; + return def; + }; + + setConfig({ + passwordType: r.passwordType || DEFAULT_CONFIG.passwordType, + charCount: String(parseInt(r.charCount, 10) || DEFAULT_CONFIG.charCount), + includeUppercase: toBool(r.includeUppercase, DEFAULT_CONFIG.includeUppercase), + includeLowercase: toBool(r.includeLowercase, DEFAULT_CONFIG.includeLowercase), + includeDigits: toBool(r.includeDigits, DEFAULT_CONFIG.includeDigits), + includeSpecialChars: toBool(r.includeSpecialChars, DEFAULT_CONFIG.includeSpecialChars), + specialCharSet: r.specialCharSet || DEFAULT_CONFIG.specialCharSet, + wordCount: String(parseInt(r.wordCount, 10) || DEFAULT_CONFIG.wordCount), + separator: r.separator !== undefined ? r.separator : DEFAULT_CONFIG.separator, + capitalizeWords: toBool(r.capitalizeWords, DEFAULT_CONFIG.capitalizeWords), + appendNumber: toBool(r.appendNumber, DEFAULT_CONFIG.appendNumber), + appendSpecialChar: toBool(r.appendSpecialChar, DEFAULT_CONFIG.appendSpecialChar), + }); + } + }, [passwordSetting.isSuccess, passwordSetting.data]); + + const set = useCallback((field, value) => { + setConfig((p) => ({ ...p, [field]: value })); + }, []); + + const isClassic = config.passwordType === PASSWORD_TYPES.CLASSIC; + + const handleSave = () => { + const normalizedConfig = normalizeConfigForBackend(config); + + passwordSave.mutate( + { + url: "/api/ExecPasswordConfig", + data: normalizedConfig, + queryKey: "PasswordSettingsPost", + } + ); + }; + + const handleBackToSettings = () => { + router.push("/cipp/settings"); + }; + + return ( + <> + + + + + +
+ Password Configuration + +
+
+ + + + + + Type + + v && set("passwordType", v)} + size="small" + color="primary" + > + Classic + Passphrase + + + + + + {isClassic + ? "Random characters from the selected classes. Good for systems requiring specific character types. 16+ characters recommended for strong security." + : "Random dictionary words joined together. Easier to remember and typically stronger at equal length. 5+ words recommended for high security."} + + + + {isClassic ? ( + <> + + { + const value = e.target.value; + if (value === '' || /^\d+$/.test(value)) { + set("charCount", value); + } + }} + size="small" + sx={{ width: 120 }} + inputProps={{ + style: { height: "40px" }, + min: 8, + max: 256 + }} + error={config.charCount === ''} + helperText={config.charCount === '' ? "Length cannot be empty" : ""} + /> + + + + set("includeUppercase", e.target.checked)} />} + label={Uppercase (A-Z)} + /> + + + set("includeLowercase", e.target.checked)} />} + label={Lowercase (a-z)} + /> + + + set("includeDigits", e.target.checked)} />} + label={Digits (0-9)} + /> + + + set("includeSpecialChars", e.target.checked)} />} + label={Special Characters} + /> + + + {config.includeSpecialChars && ( + set("specialCharSet", e.target.value)} + size="small" + fullWidth + helperText="Allowed: !@#$%^&*()-_=+/" + /> + )} + + ) : ( + <> + + { + const value = e.target.value; + if (value === '' || /^\d+$/.test(value)) { + set("wordCount", value); + } + }} + size="small" + sx={{ width: 120, maxWidth: 160 }} + inputProps={{ + style: { height: "40px" }, + min: 2, + max: 10 + }} + error={config.wordCount === ''} + helperText={config.wordCount === '' ? "Word count cannot be empty" : ""} + /> + set("separator", e.target.value)} + size="small" + sx={{ maxWidth: 120 }} + /> + + Allowed: single space, empty, or !@#$%^&*()-_=+/ + + + + + set("capitalizeWords", e.target.checked)} />} + label={Capitalize words} + /> + + + set("appendNumber", e.target.checked)} />} + label={Append number} + /> + + + set("appendSpecialChar", e.target.checked)} />} + label={Append Special Character} + /> + + + {config.appendSpecialChar && ( + set("specialCharSet", e.target.value)} + size="small" + fullWidth + helperText="Allowed: !@#$%^&*()-_=+/" + /> + )} + + )} + + + + +
+
+
+ + ); +}; + +Page.getLayout = (page) => {page}; + +export default Page; From 19e961b8409cae1871904d7cffa32cc886686c47 Mon Sep 17 00:00:00 2001 From: Luis Mengel Date: Fri, 13 Mar 2026 01:16:40 +0100 Subject: [PATCH 07/29] fix: resolve SharePoint User Information List by template instead of localized display name --- src/pages/teams-share/sharepoint/index.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/pages/teams-share/sharepoint/index.js b/src/pages/teams-share/sharepoint/index.js index b92b04c5c5aa..21cefc406ca4 100644 --- a/src/pages/teams-share/sharepoint/index.js +++ b/src/pages/teams-share/sharepoint/index.js @@ -199,11 +199,9 @@ const Page = () => { title="Site Members" queryKey={`site-members-${row.siteId}`} api={{ - url: "/api/ListGraphRequest", + url: "/api/ListSiteMembers", data: { - Endpoint: `/sites/${row.siteId}/lists/User%20Information%20List/items`, - AsApp: "true", - expand: "fields", + SiteId: row.siteId, tenantFilter: tenantFilter, }, dataKey: "Results", From 435a133680b62ae398d7423d03b9b8167488fa5b Mon Sep 17 00:00:00 2001 From: James Tarran Date: Fri, 13 Mar 2026 16:40:41 +0000 Subject: [PATCH 08/29] Add App Management Policy to standards.json Adds the AppManagementPolicy entry to standards.json with configuration fields for password addition, custom password addition, password credential max lifetime, and key credential max lifetime. Signed-off-by: James Tarran --- src/data/standards.json | 64 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/src/data/standards.json b/src/data/standards.json index 33d28185ebe6..a776d7f0c943 100644 --- a/src/data/standards.json +++ b/src/data/standards.json @@ -1343,6 +1343,70 @@ "powershellEquivalent": "Update-MgUser -UserId user@domain.com -BodyParameter @{preferredLanguage='en-US'}", "recommendedBy": [] }, + { + "name": "standards.AppManagementPolicy", + "cat": "Entra (AAD) Standards", + "tag": [], + "helpText": "Configures the default app management policy to control application and service principal credential restrictions such as password and key credential lifetimes.", + "docsDescription": "Configures the default app management policy to control application and service principal credential restrictions. This includes password addition restrictions, custom password addition, symmetric key addition, and credential lifetime limits for both applications and service principals.", + "executiveText": "Enforces credential restrictions on application registrations and service principals to limit how secrets and certificates are created and how long they remain valid. This reduces the risk of long-lived or unmanaged credentials being used to access your tenant.", + "addedComponent": [ + { + "type": "autoComplete", + "multiple": false, + "creatable": false, + "required": false, + "name": "standards.AppManagementPolicy.passwordCredentialsPasswordAddition", + "label": "Password Addition", + "options": [ + { + "label": "Enabled", + "value": "enabled" + }, + { + "label": "Disabled", + "value": "disabled" + } + ] + }, + { + "type": "autoComplete", + "multiple": false, + "creatable": false, + "required": false, + "name": "standards.AppManagementPolicy.passwordCredentialsCustomPasswordAddition", + "label": "Custom Password", + "options": [ + { + "label": "Enabled", + "value": "enabled" + }, + { + "label": "Disabled", + "value": "disabled" + } + ] + }, + { + "type": "number", + "required": false, + "name": "standards.AppManagementPolicy.passwordCredentialsMaxLifetime", + "label": "Password Credentials Max Lifetime (Days)" + }, + { + "type": "number", + "required": false, + "name": "standards.AppManagementPolicy.keyCredentialsMaxLifetime", + "label": "Key Credentials Max Lifetime (Days)" + } + ], + "label": "Set Default App Management Policy", + "impact": "Medium Impact", + "impactColour": "warning", + "addedDate": "2026-03-13", + "powershellEquivalent": "Graph API", + "recommendedBy": [] + }, { "name": "standards.OutBoundSpamAlert", "cat": "Exchange Standards", From 5980ad7551491afa37064124ca30461dfd3452f9 Mon Sep 17 00:00:00 2001 From: Bobby <31723128+kris6673@users.noreply.github.com> Date: Fri, 13 Mar 2026 18:44:40 +0100 Subject: [PATCH 09/29] fix: add SID to automatic copy-chip in tables Fixes #5576 --- 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 538116993b53..6b10e6d25ae4 100644 --- a/src/utils/get-cipp-formatting.js +++ b/src/utils/get-cipp-formatting.js @@ -430,7 +430,7 @@ export const getCippFormatting = (data, cellName, type, canReceive, flatten = tr ); } - if (cellName === "ClientId" || cellName === "role" || cellName === "appId") { + if (cellName === "ClientId" || cellName === "role" || cellName === "appId" || cellName === "SID") { return isText ? data : ; } From 12747e55a930e30d30e76fda6e513ab85655fd8c Mon Sep 17 00:00:00 2001 From: Bobby <31723128+kris6673@users.noreply.github.com> Date: Sat, 14 Mar 2026 01:11:46 +0100 Subject: [PATCH 10/29] feat: add "Operating system default" and "User Select" options to Autopilot language dropdown --- .../CippAutopilotProfileDrawer.jsx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/components/CippComponents/CippAutopilotProfileDrawer.jsx b/src/components/CippComponents/CippAutopilotProfileDrawer.jsx index 7edc95b46b60..fa1ac63baa52 100644 --- a/src/components/CippComponents/CippAutopilotProfileDrawer.jsx +++ b/src/components/CippComponents/CippAutopilotProfileDrawer.jsx @@ -109,8 +109,8 @@ export const CippAutopilotProfileDrawer = ({ {createProfile.isLoading ? "Creating..." : createProfile.isSuccess - ? "Create Another" - : "Create Profile"} + ? "Create Another" + : "Create Profile"} + + + + + ); +}; diff --git a/src/components/CippSettings/CippGDAP/CippGDAPTraceResults.jsx b/src/components/CippSettings/CippGDAP/CippGDAPTraceResults.jsx new file mode 100644 index 000000000000..36779299b190 --- /dev/null +++ b/src/components/CippSettings/CippGDAP/CippGDAPTraceResults.jsx @@ -0,0 +1,511 @@ +import React, { useState } from "react"; +import { + Box, + Card, + CardContent, + Typography, + Accordion, + AccordionSummary, + AccordionDetails, + Chip, + Stack, + Divider, + Alert, + Grid, + LinearProgress, + Tooltip, + IconButton, +} from "@mui/material"; +import { + ExpandMore, + CheckCircle, + Cancel, + Warning, + Security, + Group, + AccountTree, + InfoOutlined, +} from "@mui/icons-material"; +import { CippCodeBlock } from "../../CippComponents/CippCodeBlock"; +import { CippPathVisualization } from "./CippPathVisualization"; + +export const CippGDAPTraceResults = ({ data, isLoading, error }) => { + const [expandedRoles, setExpandedRoles] = useState({}); + const [expandedRelationships, setExpandedRelationships] = useState({}); + + if (isLoading) { + return ( + + + + Tracing GDAP access path... + + + ); + } + + if (error) { + return ( + + Error + {error} + + ); + } + + if (!data) { + return ( + + No data available. Please run the trace first. + + ); + } + + const handleRoleExpand = (roleId) => { + setExpandedRoles((prev) => ({ + ...prev, + [roleId]: !prev[roleId], + })); + }; + + const handleRelationshipExpand = (relationshipId) => { + setExpandedRelationships((prev) => ({ + ...prev, + [relationshipId]: !prev[relationshipId], + })); + }; + + const { tenantName, userUPN, userDisplayName, roles, relationships, summary, error: dataError } = data; + + if (dataError) { + return ( + + Trace completed with issues + {dataError} + + ); + } + + const getRoleStatusChip = (role) => { + if (role.isUserHasAccess) { + return } label="Has Access" color="success" size="small" />; + } else if (role.isAssigned) { + return } label="Assigned but No Access" color="warning" size="small" />; + } else if (role.roleExistsInRelationship) { + return } label="In Relationship but Not Assigned" color="info" size="small" />; + } else { + return } label="Not In Any Relationship" color="default" size="small" />; + } + }; + + const renderMembershipPath = (path) => { + if (!path || path.length === 0) return null; + + const sortedPath = [...path].sort((a, b) => { + if (a.sequence !== undefined && b.sequence !== undefined) { + return a.sequence - b.sequence; + } + return 0; + }); + + const hasMultipleGroups = sortedPath.length > 1; + + return ( + + {sortedPath.map((step, index) => ( + + + {hasMultipleGroups && step.sequence !== undefined && ( + + )} + {step.membershipType === "direct" && ( + } + label="Direct" + color="success" + size="small" + variant="outlined" + /> + )} + {step.membershipType === "nested" && ( + } + label="Nested" + color="info" + size="small" + variant="outlined" + /> + )} + {step.membershipType === "not_member" && ( + } + label="Not Member" + color="error" + size="small" + variant="outlined" + /> + )} + + {step.groupName || step.groupId} + + + {index < sortedPath.length - 1 && ( + + + ↓ + + + )} + + ))} + + ); + }; + + return ( + + {/* Summary Section */} + + + + Trace Summary + + + + + + Tenant + + + {tenantName} + + + + + User + + + {userDisplayName || userUPN} + + + + + Total Relationships + + + {summary?.totalRelationships || 0} + + + + + Roles with Access + + + {summary?.rolesWithAccess || 0} / {summary?.totalRoles || 15} + + + + + + Roles Assigned but No Access + + + + + + + + + {summary?.rolesAssignedButNoAccess || 0} + + + + + + Roles In Relationship but Not Assigned + + + + + + + + + {summary?.rolesInRelationshipButNotAssigned || 0} + + + + + + Roles Not In Any Relationship + + + + + + + + + {summary?.rolesNotInAnyRelationship || 0} + + + + + + + {/* Roles Section */} + + + + + GDAP Roles Access + + + + {roles && roles.length > 0 ? ( + roles.map((role) => ( + handleRoleExpand(role.roleId)} + > + }> + + {getRoleStatusChip(role)} + + + {role.roleName} + + {role.roleDescription && ( + + {role.roleDescription} + + )} + + + + + + {role.isUserHasAccess && role.accessPaths && role.accessPaths.length > 0 ? ( + <> + + Access Paths ({role.accessPaths.length}): + + {role.accessPaths.map((path, pathIndex) => ( + + + + ))} + + ) : role.isAssigned ? ( + <> + + Role is assigned but user does not have access through any group. + + {role.relationshipsWithRole && role.relationshipsWithRole.length > 0 && ( + <> + + Assigned Groups ({role.relationshipsWithRole.length}): + + {role.relationshipsWithRole.map((rel, relIndex) => ( + + + + ))} + + )} + + ) : role.roleExistsInRelationship ? ( + <> + + This role exists in at least one GDAP relationship but is not assigned to any groups. + + {role.relationshipsWithRoleAvailable && role.relationshipsWithRoleAvailable.length > 0 && ( + <> + Available in relationships: + {role.relationshipsWithRoleAvailable.map((rel, relIndex) => ( + + + • {rel.relationshipName} ({rel.relationshipStatus}) + + + ))} + + )} + + ) : ( + This role is not available in any GDAP relationship. + )} + + {role.relationshipsWithRole && role.relationshipsWithRole.length > 0 && ( + + + All relationships with this role: {role.relationshipsWithRole.length} + + + )} + + + + )) + ) : ( + No roles found. + )} + + + + + {/* Relationships Section */} + {relationships && relationships.length > 0 && ( + + + + + GDAP Relationships + + + + {relationships.map((relationship) => ( + handleRelationshipExpand(relationship.relationshipId)} + > + }> + + + + {relationship.relationshipName} + + + {relationship.groups?.length || 0} groups + + + + + + + + Customer Tenant: {relationship.customerTenantName || relationship.customerTenantId} + + + {relationship.groups && relationship.groups.length > 0 ? ( + <> + + Groups ({relationship.groups.length}): + + {relationship.groups.map((group, groupIndex) => { + const groupRole = roles?.find((r) => + r.relationshipsWithRole?.some((rel) => rel.groupId === group.groupId) + ); + const roleName = groupRole?.roleName || (group.roles?.[0]?.displayName || "Role"); + + return ( + + + {group.roles && group.roles.length > 1 && ( + + + Additional Roles: + + + {group.roles.slice(1).map((role, roleIndex) => ( + + ))} + + + )} + + ); + })} + + ) : ( + No groups found in this relationship. + )} + + + + ))} + + + + )} + + {/* Raw JSON View (Collapsible) */} + + + + }> + View Raw JSON + + + + + + + + + ); +}; diff --git a/src/components/CippSettings/CippGDAP/CippPathVisualization.jsx b/src/components/CippSettings/CippGDAP/CippPathVisualization.jsx new file mode 100644 index 000000000000..0dbdbb16572a --- /dev/null +++ b/src/components/CippSettings/CippGDAP/CippPathVisualization.jsx @@ -0,0 +1,266 @@ +import React from "react"; +import { Box, Typography, Chip, Stack, Paper } from "@mui/material"; +import { + Person, + Group, + Security, + AccountTree, + CheckCircle, + Cancel, + Warning, +} from "@mui/icons-material"; +import { CippFlowDiagram } from "./CippFlowDiagram"; + +/** + * Visual Path Component for GDAP Access Traces + * Shows a visual representation of the access path from User → Groups → Role + */ +export const CippPathVisualization = ({ + userDisplayName, + userUPN, + membershipPath = [], + groupName, + roleName, + relationshipName, + customerTenantName, + isMember = true, + ...other +}) => { + // Color scheme matching sankey diagrams + const colors = { + user: "hsl(28, 100%, 53%)", // Orange - enabled users + success: "hsl(99, 70%, 50%)", // Green - compliant, has access + error: "hsl(0, 100%, 50%)", // Red - errors, no access + info: "hsl(200, 70%, 50%)", // Blue - nested groups, info + warning: "hsl(39, 100%, 50%)", // Yellow/Orange - warnings + teal: "hsl(140, 70%, 50%)", // Teal - security defaults + grey: "hsl(0, 0%, 60%)", // Grey - disabled + }; + + if (!membershipPath || membershipPath.length === 0) { + // Fallback: show simple path even without detailed membership path + const nodes = [ + { + id: "user", + label: userDisplayName || userUPN, + subLabel: "User", + icon: , + backgroundColor: `${colors.user}20`, // 20% opacity + borderColor: colors.user, + chips: [], + }, + { + id: "group", + label: groupName || "Unknown Group", + subLabel: "Security Group", + icon: isMember ? : , + backgroundColor: isMember ? `${colors.success}20` : `${colors.error}20`, + borderColor: isMember ? colors.success : colors.error, + chips: [ + { + label: isMember ? "Member" : "Not Member", + sx: { backgroundColor: isMember ? colors.success : colors.error, color: "white" }, + size: "small", + }, + ], + }, + { + id: "role", + label: roleName || "Role", + subLabel: "GDAP Role", + icon: , + backgroundColor: isMember ? `${colors.success}20` : `${colors.grey}20`, + borderColor: isMember ? colors.success : colors.grey, + chips: [ + { + label: isMember ? "Has Access" : "No Access", + sx: { backgroundColor: isMember ? colors.success : colors.grey, color: "white" }, + size: "small", + }, + ], + }, + ]; + + return ( + + {relationshipName && ( + + Relationship: {relationshipName} + {customerTenantName && ` → ${customerTenantName}`} + + )} + + + ); + } + + // Sort path by sequence if available + const sortedPath = [...membershipPath].sort((a, b) => { + if (a.sequence !== undefined && b.sequence !== undefined) { + return a.sequence - b.sequence; + } + return 0; + }); + + // Build nodes for the flow diagram + const nodes = []; + + // Start with user node + nodes.push({ + id: "user", + label: userDisplayName || userUPN, + subLabel: "User", + icon: , + backgroundColor: `${colors.user}20`, + borderColor: colors.user, + chips: [], + }); + + // Add group nodes from the path + sortedPath.forEach((step, index) => { + const isFirstGroup = index === 0; + const isLastGroup = index === sortedPath.length - 1; + const isDirect = step.membershipType === "direct"; + const isNested = step.membershipType === "nested"; + const isNotMember = step.membershipType === "not_member"; + const isIntermediate = !isFirstGroup && !isLastGroup; + + const chips = []; + + if (isNotMember) { + chips.push({ + label: "Not Member", + sx: { backgroundColor: colors.error, color: "white" }, + icon: , + }); + } else if (isDirect) { + chips.push({ + label: "Direct", + sx: { backgroundColor: colors.success, color: "white" }, + icon: , + }); + } else if (isNested) { + if (isLastGroup && sortedPath.length === 1) { + // no chip + } else { + chips.push({ + label: "Nested", + sx: { backgroundColor: colors.info, color: "white" }, + icon: , + }); + } + } + + if (sortedPath.length > 1 && step.sequence !== undefined) { + chips.push({ + label: `Step ${step.sequence + 1}`, + variant: "outlined", + size: "small", + sx: { borderColor: colors.info, color: colors.info, fontWeight: "bold" }, + }); + } + + if (isLastGroup && !isNotMember) { + chips.push({ + label: "GDAP Mapped", + sx: { backgroundColor: colors.teal, color: "white", fontWeight: "bold" }, + size: "small", + }); + } + + let groupColor; + let subLabel; + let nodeElevation = 2; + let nodeBorderWidth = 0; + + if (isNotMember) { + groupColor = colors.error; + subLabel = "Target Group (No Access)"; + } else if (isLastGroup) { + groupColor = colors.teal; + if (isDirect) { + subLabel = "GDAP Mapped Group (Direct)"; + } else if (isNested) { + subLabel = + sortedPath.length === 1 ? "GDAP Mapped Group (User Nested)" : "GDAP Mapped Group (Nested)"; + } else { + subLabel = "GDAP Mapped Group"; + } + nodeElevation = 4; + nodeBorderWidth = 3; + } else if (isFirstGroup && isDirect) { + groupColor = colors.success; + subLabel = "User's Direct Group"; + } else if (isFirstGroup && isNested) { + groupColor = colors.info; + subLabel = "User's Group (via nesting)"; + } else if (isIntermediate) { + groupColor = colors.info; + subLabel = "Intermediate Group"; + nodeElevation = 1; + } else { + groupColor = colors.info; + subLabel = "Group"; + } + + nodes.push({ + id: `group-${step.groupId || index}`, + label: step.groupName || step.groupId || "Unknown Group", + subLabel: subLabel, + icon: + isNotMember ? ( + + ) : ( + + ), + backgroundColor: `${groupColor}${isLastGroup ? "30" : "20"}`, + borderColor: groupColor, + borderWidth: nodeBorderWidth, + elevation: nodeElevation, + chips: chips, + }); + }); + + const hasAccess = !sortedPath.some((step) => step.membershipType === "not_member"); + const roleColor = hasAccess ? colors.success : colors.grey; + nodes.push({ + id: "role", + label: roleName || "Role", + subLabel: "GDAP Role", + icon: , + backgroundColor: `${roleColor}20`, + borderColor: roleColor, + chips: [ + { + label: hasAccess ? "Has Access" : "No Access", + sx: { backgroundColor: roleColor, color: "white" }, + icon: hasAccess ? : , + }, + ], + }); + + return ( + + {relationshipName && ( + + + + Relationship: {relationshipName} + + {customerTenantName && ( + <> + + → + + + Customer: {customerTenantName} + + + )} + + + )} + + + ); +}; diff --git a/src/pages/cipp/settings/tenants.js b/src/pages/cipp/settings/tenants.js index 3ce10da19c2d..c6cd965fb2ac 100644 --- a/src/pages/cipp/settings/tenants.js +++ b/src/pages/cipp/settings/tenants.js @@ -1,12 +1,201 @@ +import { Button, SvgIcon } from "@mui/material"; +import { CippTablePage } from "../../../components/CippComponents/CippTablePage.jsx"; +import { CippApiDialog } from "../../../components/CippComponents/CippApiDialog"; +import { useCippGDAPTrace } from "../../../components/CippSettings/CippGDAP/CippGDAPTrace"; import { Layout as DashboardLayout } from "../../../layouts/index.js"; import { TabbedLayout } from "../../../layouts/TabbedLayout"; import tabOptions from "./tabOptions"; -import { CippTenantTable } from "../../../components/CippWizard/CippTenantTable"; +import { useDialog } from "../../../hooks/use-dialog"; +import { + Sync, + Block, + PlayArrow, + RestartAlt, + Delete, + Add, + Refresh, +} from "@mui/icons-material"; +import cacheTypes from "../../../data/CIPPDBCacheTypes.json"; const Page = () => { const pageTitle = "Tenants - Backend"; + const createDialog = useDialog(); + const { ref: gdapRef, traceGdapAction, CippGDAPTrace } = useCippGDAPTrace(); - return ; + const actions = [ + { + label: "Exclude Tenants", + type: "POST", + url: "/api/ExecExcludeTenant?AddExclusion=true", + icon: , + data: { value: "customerId" }, + confirmText: "Are you sure you want to exclude [displayName]?", + multiPost: false, + condition: (row) => row.displayName !== "*Partner Tenant", + }, + { + label: "Include Tenants", + type: "POST", + url: "/api/ExecExcludeTenant?RemoveExclusion=true", + icon: , + data: { value: "customerId" }, + confirmText: "Are you sure you want to include [displayName]?", + multiPost: false, + condition: (row) => row.displayName !== "*Partner Tenant", + }, + { + label: "Refresh CPV Permissions", + type: "POST", + url: "/api/ExecCPVPermissions", + icon: , + data: { tenantFilter: "customerId" }, + confirmText: "Are you sure you want to refresh the CPV permissions for [displayName]?", + multiPost: false, + }, + { + label: "Reset CPV Permissions", + type: "POST", + url: "/api/ExecCPVPermissions?&ResetSP=true", + icon: , + data: { tenantFilter: "customerId" }, + confirmText: + "Are you sure you want to reset the CPV permissions for [displayName]? (This will delete the Service Principal and re-add it.)", + multiPost: false, + condition: (row) => + row.displayName !== "*Partner Tenant" && row.delegatedPrivilegeStatus !== "directTenant", + }, + { + label: "Remove Tenant", + type: "POST", + url: "/api/ExecRemoveTenant", + icon: , + data: { TenantID: "customerId" }, + confirmText: + "Are you sure you want to remove [displayName]? If this is a Direct Tenant, this will no longer be accessible until you add it via the Setup Wizard.", + multiPost: false, + condition: (row) => row.displayName !== "*Partner Tenant", + }, + { + label: "Refresh CIPPDB Cache", + type: "GET", + url: "/api/ExecCIPPDBCache", + icon: , + data: { Name: "Name", TenantFilter: "customerId" }, + confirmText: "Select the cache type to refresh for [displayName]:", + multiPost: false, + hideBulk: true, + fields: [ + { + type: "autoComplete", + name: "Name", + label: "Cache Type", + placeholder: "Select a cache type", + options: cacheTypes.map((cacheType) => ({ + label: cacheType.friendlyName, + value: cacheType.type, + description: cacheType.description, + })), + multiple: false, + creatable: false, + required: true, + }, + ], + customDataformatter: (rowData, actionData, formData) => { + const tenantFilter = rowData?.customerId || rowData?.defaultDomainName || ""; + const cacheTypeName = formData.Name?.value || formData.Name || ""; + return { + Name: cacheTypeName, + TenantFilter: tenantFilter, + }; + }, + }, + traceGdapAction, + ]; + + const offCanvas = { + extendedInfoFields: [ + "displayName", + "defaultDomainName", + "delegatedPrivilegeStatus", + "Excluded", + "ExcludeDate", + "ExcludeUser", + ], + actions: actions, + }; + + const simpleColumns = [ + "displayName", + "defaultDomainName", + "delegatedPrivilegeStatus", + "Excluded", + "ExcludeDate", + "ExcludeUser", + ]; + + const filters = [ + { + filterName: "Included tenants", + value: [{ id: "Excluded", value: "No" }], + type: "column", + }, + { + filterName: "Excluded tenants", + value: [{ id: "Excluded", value: "Yes" }], + type: "column", + }, + ]; + + return ( + <> + + + + + Force Refresh + + } + /> + + + + ); }; Page.getLayout = (page) => ( From ab20286fca467b964b277e2d2f0cc1bd283a77a5 Mon Sep 17 00:00:00 2001 From: Roel van der Wegen Date: Mon, 16 Mar 2026 13:58:24 +0100 Subject: [PATCH 19/29] Have portals button use existing info --- src/pages/dashboardv2/index.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/pages/dashboardv2/index.js b/src/pages/dashboardv2/index.js index eeb351d6f3a2..fcb983b4e36d 100644 --- a/src/pages/dashboardv2/index.js +++ b/src/pages/dashboardv2/index.js @@ -100,8 +100,12 @@ const Page = () => { }); const currentTenantInfo = ApiGetCall({ - url: "/api/ListTenants", - queryKey: `ListTenants`, + url: "/api/listTenants", + data: { AllTenantSelector: true }, + queryKey: "TenantSelector", + refetchOnMount: false, + refetchOnReconnect: false, + keepPreviousData: true, }); const reportData = From 3de126e6f9f1ec9fc619cf6f2439ed95d65a6057 Mon Sep 17 00:00:00 2001 From: Bobby <31723128+kris6673@users.noreply.github.com> Date: Mon, 16 Mar 2026 17:29:08 +0100 Subject: [PATCH 20/29] fix(settings): resolve number field focus loss on retention settings Inline RetentionControls JSX directly into the CardButton prop in CippBackupRetentionSettings and CippLogRetentionSettings, replacing inner component definitions that caused React to unmount/remount the TextField on every keystroke due to unstable component type identity. Also removes dead imports (ButtonGroup, SvgIcon, History) from CippBackupRetentionSettings that were leftover from an earlier draft. --- .../CippBackupRetentionSettings.jsx | 59 +++++++++---------- .../CippSettings/CippLogRetentionSettings.jsx | 56 ++++++++---------- 2 files changed, 53 insertions(+), 62 deletions(-) diff --git a/src/components/CippSettings/CippBackupRetentionSettings.jsx b/src/components/CippSettings/CippBackupRetentionSettings.jsx index b49ad30cf556..5b9310a85728 100644 --- a/src/components/CippSettings/CippBackupRetentionSettings.jsx +++ b/src/components/CippSettings/CippBackupRetentionSettings.jsx @@ -1,8 +1,7 @@ -import { Button, ButtonGroup, SvgIcon, Typography, TextField, Box } from "@mui/material"; +import { Button, Typography, TextField, Box } from "@mui/material"; import CippButtonCard from "../CippCards/CippButtonCard"; import { ApiGetCall, ApiPostCall } from "../../api/ApiCall"; import { CippApiResults } from "../CippComponents/CippApiResults"; -import { History } from "@mui/icons-material"; import { useState, useEffect } from "react"; const CippBackupRetentionSettings = () => { @@ -55,40 +54,36 @@ const CippBackupRetentionSettings = () => { } }; - const RetentionControls = () => { - return ( - - - - - ); - }; - return ( } + CardButton={ + + + + + } > Configure how long to keep backup files. Both CIPP system backups and tenant backups will be diff --git a/src/components/CippSettings/CippLogRetentionSettings.jsx b/src/components/CippSettings/CippLogRetentionSettings.jsx index a45b0c45bea3..3f949ed0c57b 100644 --- a/src/components/CippSettings/CippLogRetentionSettings.jsx +++ b/src/components/CippSettings/CippLogRetentionSettings.jsx @@ -61,40 +61,36 @@ const CippLogRetentionSettings = () => { } }; - const RetentionControls = () => { - return ( - - - - - ); - }; - return ( } + CardButton={ + + + + + } > Configure how long to keep CIPP log entries. Logs will be automatically deleted after this From 24b0b8bdc3a47e15bc0b1c802bea3bcbbb27f018 Mon Sep 17 00:00:00 2001 From: Luis Mengel Date: Mon, 16 Mar 2026 23:13:21 +0100 Subject: [PATCH 21/29] Add "Show Usage" toggle to Tenant Groups page --- .../tenant/administration/tenants/groups/index.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/pages/tenant/administration/tenants/groups/index.js b/src/pages/tenant/administration/tenants/groups/index.js index ca49669462af..5095155edfef 100644 --- a/src/pages/tenant/administration/tenants/groups/index.js +++ b/src/pages/tenant/administration/tenants/groups/index.js @@ -2,7 +2,7 @@ import { Layout as DashboardLayout } from "../../../../../layouts/index.js"; import { TabbedLayout } from "../../../../../layouts/TabbedLayout"; import { CippTablePage } from "../../../../../components/CippComponents/CippTablePage.jsx"; import tabOptions from "../tabOptions"; -import { Edit, PlayArrow, GroupAdd } from "@mui/icons-material"; +import { Edit, PlayArrow, GroupAdd, ViewList } from "@mui/icons-material"; import { TrashIcon } from "@heroicons/react/24/outline"; import { CippAddTenantGroupDrawer } from "../../../../../components/CippComponents/CippAddTenantGroupDrawer"; import { CippApiLogsDrawer } from "../../../../../components/CippComponents/CippApiLogsDrawer"; @@ -10,12 +10,16 @@ import { CippTenantGroupOffCanvas } from "../../../../../components/CippComponen import { CippApiDialog } from "../../../../../components/CippComponents/CippApiDialog.jsx"; import { Box, Button } from "@mui/material"; import { useDialog } from "../../../../../hooks/use-dialog.js"; +import { useState } from "react" const Page = () => { const pageTitle = "Tenant Groups"; const createDefaultGroupsDialog = useDialog(); + const [showUsage, setShowUsage] = useState(false); - const simpleColumns = ["Name", "Description", "GroupType", "Members"]; + const simpleColumns = showUsage + ? ["Name", "Description", "GroupType", "Members", "Usage"] + : ["Name", "Description", "GroupType", "Members"]; const offcanvas = { children: (row) => { @@ -57,12 +61,16 @@ const Page = () => { tenantInTitle={false} simpleColumns={simpleColumns} apiUrl="/api/ListTenantGroups" - queryKey="TenantGroupListPage" + apiData={{ includeUsage: showUsage }} + queryKey={showUsage ? "TenantGroupListPage-usage" : "TenantGroupListPage"} apiDataKey="Results" actions={actions} cardButton={ + From eb2f11382e6c6564b9a7f7109f27611d74afc748 Mon Sep 17 00:00:00 2001 From: Roel van der Wegen Date: Tue, 17 Mar 2026 11:44:06 +0100 Subject: [PATCH 22/29] GDAP trace fix --- .../CippGDAP/CippGDAPTraceResults.jsx | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/components/CippSettings/CippGDAP/CippGDAPTraceResults.jsx b/src/components/CippSettings/CippGDAP/CippGDAPTraceResults.jsx index 36779299b190..ff34b407388a 100644 --- a/src/components/CippSettings/CippGDAP/CippGDAPTraceResults.jsx +++ b/src/components/CippSettings/CippGDAP/CippGDAPTraceResults.jsx @@ -28,6 +28,7 @@ import { } from "@mui/icons-material"; import { CippCodeBlock } from "../../CippComponents/CippCodeBlock"; import { CippPathVisualization } from "./CippPathVisualization"; +import { getCippRoleTranslation } from "../../../utils/get-cipp-role-translation"; export const CippGDAPTraceResults = ({ data, isLoading, error }) => { const [expandedRoles, setExpandedRoles] = useState({}); @@ -437,7 +438,14 @@ export const CippGDAPTraceResults = ({ data, isLoading, error }) => { const groupRole = roles?.find((r) => r.relationshipsWithRole?.some((rel) => rel.groupId === group.groupId) ); - const roleName = groupRole?.roleName || (group.roles?.[0]?.displayName || "Role"); + const firstRoleDef = group.roles?.[0]; + const roleName = + groupRole?.roleName || + firstRoleDef?.displayName || + (firstRoleDef?.roleDefinitionId + ? getCippRoleTranslation(firstRoleDef.roleDefinitionId) + : null) || + "Role"; return ( @@ -464,7 +472,13 @@ export const CippGDAPTraceResults = ({ data, isLoading, error }) => { {group.roles.slice(1).map((role, roleIndex) => ( From 938cc4d1fbc20c4c53d4498118d214a3a544b06f Mon Sep 17 00:00:00 2001 From: Bobby <31723128+kris6673@users.noreply.github.com> Date: Tue, 17 Mar 2026 16:27:44 +0100 Subject: [PATCH 23/29] fix: enable Next Step button after template selection Fixes #5594 --- .../CippWizard/CippWizardGroupTemplates.jsx | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/components/CippWizard/CippWizardGroupTemplates.jsx b/src/components/CippWizard/CippWizardGroupTemplates.jsx index 16e1bd61ef0d..2e1679808723 100644 --- a/src/components/CippWizard/CippWizardGroupTemplates.jsx +++ b/src/components/CippWizard/CippWizardGroupTemplates.jsx @@ -4,11 +4,12 @@ import CippFormComponent from "../CippComponents/CippFormComponent"; import { CippFormCondition } from "../CippComponents/CippFormCondition"; import { Grid } from "@mui/system"; import { useWatch } from "react-hook-form"; -import { useEffect } from "react"; +import { useEffect, useRef } from "react"; export const CippWizardGroupTemplates = (props) => { const { postUrl, formControl, onPreviousStep, onNextStep, currentStep } = props; const watcher = useWatch({ control: formControl.control, name: "TemplateList" }); + const lastAppliedTemplate = useRef(null); const groupOptions = [ { label: "Dynamic Group", value: "dynamic" }, { label: "Dynamic Distribution Group", value: "dynamicDistribution" }, @@ -18,19 +19,28 @@ export const CippWizardGroupTemplates = (props) => { { label: "Mail Enabled Security Group", value: "security" }, ]; useEffect(() => { - if (watcher?.value) { + if (watcher?.value && watcher.value !== lastAppliedTemplate.current) { + lastAppliedTemplate.current = watcher.value; console.log("Loading template:", watcher); // Set groupType first to ensure conditional fields are visible - formControl.setValue("groupType", watcher.addedFields.groupType); + formControl.setValue("groupType", watcher.addedFields.groupType, { shouldValidate: true }); // Use setTimeout to ensure the DOM updates with the groupType before setting other fields setTimeout(() => { - formControl.setValue("displayName", watcher.addedFields.displayName); - formControl.setValue("description", watcher.addedFields.description); - formControl.setValue("username", watcher.addedFields.username); - formControl.setValue("allowExternal", watcher.addedFields.allowExternal); - formControl.setValue("membershipRules", watcher.addedFields.membershipRules); + formControl.setValue("displayName", watcher.addedFields.displayName, { + shouldValidate: true, + }); + formControl.setValue("description", watcher.addedFields.description, { + shouldValidate: true, + }); + formControl.setValue("username", watcher.addedFields.username, { shouldValidate: true }); + formControl.setValue("allowExternal", watcher.addedFields.allowExternal, { + shouldValidate: true, + }); + formControl.setValue("membershipRules", watcher.addedFields.membershipRules, { + shouldValidate: true, + }); console.log("Set membershipRules to:", watcher.addedFields.membershipRules); }, 100); From 0f6d9a4b2b5535e99720cf061f158eb07b81d521 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 17 Mar 2026 16:59:07 -0400 Subject: [PATCH 24/29] feat: enhance user offboarding functionality add table view for all offboarding tasks for a tenant implement dialog mode for CippWizard --- .../CippWizard/CippWizardDialogContext.js | 10 ++ .../CippWizard/CippWizardOffboarding.jsx | 66 +++++------ src/components/CippWizard/CippWizardPage.jsx | 75 +++++++++++-- .../CippWizard/CippWizardStepButtons.jsx | 92 ++++++++++------ src/pages/cipp/scheduler/index.js | 10 +- .../offboarding-wizard/index.js | 104 +++++++++++++++++- .../administration/vacation-mode/index.js | 16 +-- 7 files changed, 288 insertions(+), 85 deletions(-) create mode 100644 src/components/CippWizard/CippWizardDialogContext.js diff --git a/src/components/CippWizard/CippWizardDialogContext.js b/src/components/CippWizard/CippWizardDialogContext.js new file mode 100644 index 000000000000..2ff78f19fe1b --- /dev/null +++ b/src/components/CippWizard/CippWizardDialogContext.js @@ -0,0 +1,10 @@ +import { createContext, useContext } from "react"; + +/** + * When CippWizardPage is used in dialogMode, it provides this context with a + * reference to the DialogActions DOM node. CippWizardStepButtons checks for + * it and portals its navigation buttons there, keeping the main content area + * clean while anchoring controls at the bottom of the dialog. + */ +export const CippWizardDialogContext = createContext(null); +export const useCippWizardDialog = () => useContext(CippWizardDialogContext); diff --git a/src/components/CippWizard/CippWizardOffboarding.jsx b/src/components/CippWizard/CippWizardOffboarding.jsx index d17e2fca4c0f..9c275d4a563b 100644 --- a/src/components/CippWizard/CippWizardOffboarding.jsx +++ b/src/components/CippWizard/CippWizardOffboarding.jsx @@ -358,7 +358,7 @@ export const CippWizardOffboarding = (props) => { compareType="is" compareValue={true} > - + Scheduled Offboarding Date { fullWidth /> + - - Send results to: - - - - + + Send results to: + + + + - - - - + + + diff --git a/src/components/CippWizard/CippWizardPage.jsx b/src/components/CippWizard/CippWizardPage.jsx index 6266a3ec1c4b..f21b6876f01f 100644 --- a/src/components/CippWizard/CippWizardPage.jsx +++ b/src/components/CippWizard/CippWizardPage.jsx @@ -1,8 +1,24 @@ -import { Box, Button, Container, Stack, SvgIcon } from "@mui/material"; +import { + Box, + Button, + Container, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + Divider, + IconButton, + Stack, + SvgIcon, + useMediaQuery, +} from "@mui/material"; +import { Close } from "@mui/icons-material"; import { CippWizard } from "./CippWizard"; import { useRouter } from "next/router"; import { ArrowLeftIcon } from "@mui/x-date-pickers"; import { CippHead } from "../CippComponents/CippHead"; +import { CippWizardDialogContext } from "./CippWizardDialogContext"; +import { useState, useCallback } from "react"; const CippWizardPage = (props) => { const router = useRouter(); @@ -14,8 +30,56 @@ const CippWizardPage = (props) => { backButton = true, wizardOrientation = "horizontal", maxWidth = "xl", + dialogMode = false, + open = false, + onClose, + dialogIcon, + relatedQueryKeys, ...other } = props; + + const mdDown = useMediaQuery((theme) => theme.breakpoints.down("md")); + const [actionsEl, setActionsEl] = useState(null); + const actionsRef = useCallback((el) => setActionsEl(el), []); + + const wizardNode = ( + + ); + + if (dialogMode) { + return ( + + + {dialogIcon} + {wizardTitle} + + + + + + + + {wizardNode} + + + + + + ); + } + return ( <> @@ -30,12 +94,9 @@ const CippWizardPage = (props) => { - + + {wizardNode} + diff --git a/src/components/CippWizard/CippWizardStepButtons.jsx b/src/components/CippWizard/CippWizardStepButtons.jsx index bf0e7f3a7918..64b8f0e23703 100644 --- a/src/components/CippWizard/CippWizardStepButtons.jsx +++ b/src/components/CippWizard/CippWizardStepButtons.jsx @@ -1,7 +1,9 @@ import { Button, Stack } from "@mui/material"; import { useFormState } from "react-hook-form"; +import { createPortal } from "react-dom"; import { ApiPostCall } from "../../api/ApiCall"; import { CippApiResults } from "../CippComponents/CippApiResults"; +import { useCippWizardDialog } from "./CippWizardDialogContext"; export const CippWizardStepButtons = (props) => { const { @@ -19,7 +21,18 @@ export const CippWizardStepButtons = (props) => { ...other } = props; const { isValid, isSubmitted, isSubmitting } = useFormState({ control: formControl.control }); - const sendForm = ApiPostCall({ relatedQueryKeys: queryKeys }); + const dialogContext = useCippWizardDialog(); + const mergedQueryKeys = [ + ...(Array.isArray(queryKeys) ? queryKeys : queryKeys ? [queryKeys] : []), + ...(Array.isArray(dialogContext?.relatedQueryKeys) + ? dialogContext.relatedQueryKeys + : dialogContext?.relatedQueryKeys + ? [dialogContext.relatedQueryKeys] + : []), + ]; + const sendForm = ApiPostCall({ + relatedQueryKeys: mergedQueryKeys.length ? mergedQueryKeys : undefined, + }); const handleSubmit = () => { const values = formControl.getValues(); const newData = {}; @@ -33,40 +46,55 @@ export const CippWizardStepButtons = (props) => { sendForm.mutate({ url: postUrl, data: newData }); }; + const buttonStack = ( + + {dialogContext?.onClose && ( + + )} + {currentStep > 0 && ( + + )} + {!noNextButton && currentStep !== lastStep && ( + + )} + {!noSubmitButton && currentStep === lastStep && ( +
+ +
+ )} +
+ ); + return ( <> - - {currentStep > 0 && ( - - )} - {!noNextButton && currentStep !== lastStep && ( - - )} - {!noSubmitButton && currentStep === lastStep && ( -
- -
- )} -
+ {dialogContext?.actionsEl ? createPortal(buttonStack, dialogContext.actionsEl) : buttonStack} ); }; diff --git a/src/pages/cipp/scheduler/index.js b/src/pages/cipp/scheduler/index.js index f3a877ea79ba..73b2396f5171 100644 --- a/src/pages/cipp/scheduler/index.js +++ b/src/pages/cipp/scheduler/index.js @@ -6,10 +6,12 @@ import { useState } from "react"; import ScheduledTaskDetails from "../../../components/CippComponents/ScheduledTaskDetails"; import { CippScheduledTaskActions } from "../../../components/CippComponents/CippScheduledTaskActions"; import { CippSchedulerDrawer } from "../../../components/CippComponents/CippSchedulerDrawer"; +import { useSettings } from "../../../hooks/use-settings"; const Page = () => { const [editTaskId, setEditTaskId] = useState(null); const [cloneTaskId, setCloneTaskId] = useState(null); + const currentTenant = useSettings().currentTenant; const drawerHandlers = { openEditDrawer: (row) => { @@ -67,9 +69,13 @@ const Page = () => { tenantInTitle={false} title="Scheduled Tasks" apiUrl={ - showHiddenJobs ? "/api/ListScheduledItems?ShowHidden=true" : "/api/ListScheduledItems" + showHiddenJobs ? `/api/ListScheduledItems?ShowHidden=true` : `/api/ListScheduledItems` + } + queryKey={ + showHiddenJobs + ? `ListScheduledItems-hidden-${currentTenant}` + : `ListScheduledItems-${currentTenant}` } - queryKey={showHiddenJobs ? `ListScheduledItems-hidden` : `ListScheduledItems`} simpleColumns={[ "ExecutedTime", "TaskState", diff --git a/src/pages/identity/administration/offboarding-wizard/index.js b/src/pages/identity/administration/offboarding-wizard/index.js index 931cfded2eef..496829ca670e 100644 --- a/src/pages/identity/administration/offboarding-wizard/index.js +++ b/src/pages/identity/administration/offboarding-wizard/index.js @@ -5,9 +5,28 @@ import { CippTenantStep } from "../../../../components/CippWizard/CippTenantStep import { CippWizardAutoComplete } from "../../../../components/CippWizard/CippWizardAutoComplete"; import { CippWizardOffboarding } from "../../../../components/CippWizard/CippWizardOffboarding"; import { useSettings } from "../../../../hooks/use-settings"; +import CippTablePage from "../../../../components/CippComponents/CippTablePage"; +import { PersonOff } from "@mui/icons-material"; +import { Button } from "@mui/material"; +import { useState } from "react"; +import ScheduledTaskDetails from "../../../../components/CippComponents/ScheduledTaskDetails"; +import { CippScheduledTaskActions } from "../../../../components/CippComponents/CippScheduledTaskActions"; +import { CippSchedulerDrawer } from "../../../../components/CippComponents/CippSchedulerDrawer"; const Page = () => { + const [wizardOpen, setWizardOpen] = useState(false); + const [editTaskId, setEditTaskId] = useState(null); + const [cloneTaskId, setCloneTaskId] = useState(null); const initialState = useSettings(); + const currentTenant = initialState.currentTenant; + + const drawerHandlers = { + openEditDrawer: (row) => setEditTaskId(row.RowKey), + openCloneDrawer: (row) => setCloneTaskId(row.RowKey), + }; + + const actions = CippScheduledTaskActions(drawerHandlers); + const steps = [ { title: "Step 1", @@ -61,13 +80,96 @@ const Page = () => { }, ]; + const filterList = [ + { + filterName: "Running", + value: [{ id: "TaskState", value: "Running" }], + type: "column", + }, + { + filterName: "Planned", + value: [{ id: "TaskState", value: "Planned" }], + type: "column", + }, + { + filterName: "Failed", + value: [{ id: "TaskState", value: "Failed" }], + type: "column", + }, + { + filterName: "Completed", + value: [{ id: "TaskState", value: "Completed" }], + type: "column", + }, + ]; + + const offCanvas = { + children: (extendedData) => ( + + ), + size: "xl", + actions: actions, + }; + return ( <> + setWizardOpen(true)} startIcon={}> + Start Offboarding + + } + title="User Offboarding" + apiUrl="/api/ListScheduledItems?Type=Invoke-CIPPOffboardingJob&" + queryKey={`OffboardingJobs-${currentTenant}`} + actions={actions} + simpleColumns={[ + "Tenant", + "Parameters.Username", + "TaskState", + "ScheduledTime", + "ExecutedTime", + ]} + filters={filterList} + offCanvas={offCanvas} + /> + + {/* Edit Drawer */} + {editTaskId && ( + setEditTaskId(null)} + onClose={() => setEditTaskId(null)} + PermissionButton={({ children }) => <>{children}} + /> + )} + + {/* Clone Drawer */} + {cloneTaskId && ( + setCloneTaskId(null)} + onClose={() => setCloneTaskId(null)} + PermissionButton={({ children }) => <>{children}} + /> + )} setWizardOpen(false)} + dialogIcon={} + relatedQueryKeys={[`OffboardingJobs-${currentTenant}`]} + initialState={{ + ...initialState.offboardingDefaults, + ...{ Scheduled: { enabled: false } }, + }} steps={steps} postUrl="/api/ExecOffboardUser" wizardTitle="User Offboarding Wizard" + fullScreen /> ); diff --git a/src/pages/identity/administration/vacation-mode/index.js b/src/pages/identity/administration/vacation-mode/index.js index 1b85952156e1..f2caa0ec2468 100644 --- a/src/pages/identity/administration/vacation-mode/index.js +++ b/src/pages/identity/administration/vacation-mode/index.js @@ -5,8 +5,12 @@ import { EyeIcon } from "@heroicons/react/24/outline"; import { Button } from "@mui/material"; import Link from "next/link"; import { EventAvailable } from "@mui/icons-material"; +import { useSettings } from "../../../../hooks/use-settings.js"; const Page = () => { + const initialState = useSettings(); + const currentTenant = initialState.currentTenant; + const actions = [ { label: "View Task Details", @@ -76,17 +80,9 @@ const Page = () => { } title="Vacation Mode" apiUrl="/api/ListScheduledItems?SearchTitle=*Vacation*" - queryKey="VacationMode" - tenantInTitle={false} + queryKey={`VacationMode-${currentTenant}`} actions={actions} - simpleColumns={[ - "Tenant", - "Name", - "Reference", - "TaskState", - "ScheduledTime", - "ExecutedTime", - ]} + simpleColumns={["Tenant", "Name", "Reference", "TaskState", "ScheduledTime", "ExecutedTime"]} filters={filterList} offCanvas={{ extendedInfoFields: [ From 8400d20d93fc6845e5730a073b01e3807992be3d Mon Sep 17 00:00:00 2001 From: Luis Mengel Date: Tue, 17 Mar 2026 21:59:30 +0100 Subject: [PATCH 25/29] improve user management table filters for businessPhones and assignedLicenses --- src/utils/get-cipp-filter-variant.js | 27 +++++++++++++++++++++++++-- src/utils/get-cipp-formatting.js | 17 +++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/utils/get-cipp-filter-variant.js b/src/utils/get-cipp-filter-variant.js index ab59e94bcc32..539fbd464cfb 100644 --- a/src/utils/get-cipp-filter-variant.js +++ b/src/utils/get-cipp-filter-variant.js @@ -50,6 +50,12 @@ export const getCippFilterVariant = (providedColumnKeys, arg) => { })); } + // Add "No Licenses Assigned" option at beginning + filterSelectOptions.unshift({ + label: "No Licenses Assigned", + value: "__no_license__", + }); + return { filterVariant: "multi-select", sortingFn: "alphanumeric", @@ -58,11 +64,28 @@ export const getCippFilterVariant = (providedColumnKeys, arg) => { if (!filterValue || !Array.isArray(filterValue) || filterValue.length === 0) { return true; } - if (!userLicenses || !Array.isArray(userLicenses) || userLicenses.length === 0) { + + const hasNoLicenseFilter = filterValue.includes("__no_license__"); + const otherFilters = filterValue.filter((v) => v !== "__no_license__"); + const isUnlicensed = !userLicenses || !Array.isArray(userLicenses) || userLicenses.length === 0; + + // If user selected "No Licenses Assigned" and this user is unlicensed → match + if (hasNoLicenseFilter && isUnlicensed) { + return true; + } + + // If only "No Licenses Assigned" is selected and user has licenses → no match + if (hasNoLicenseFilter && otherFilters.length === 0 && !isUnlicensed) { return false; } + + // Check other license filters + if (isUnlicensed) { + return false; + } + const userSkuIds = userLicenses.map((license) => license.skuId).filter(Boolean); - return filterValue.some((selectedSkuId) => userSkuIds.includes(selectedSkuId)); + return otherFilters.some((selectedSkuId) => userSkuIds.includes(selectedSkuId)); }, filterSelectOptions: filterSelectOptions, }; diff --git a/src/utils/get-cipp-formatting.js b/src/utils/get-cipp-formatting.js index 6b10e6d25ae4..217b1ae2a6a1 100644 --- a/src/utils/get-cipp-formatting.js +++ b/src/utils/get-cipp-formatting.js @@ -643,6 +643,23 @@ export const getCippFormatting = (data, cellName, type, canReceive, flatten = tr ); } + // Handle businessPhones + if (cellName === "businessPhones") { + if (!Array.isArray(data)) { + data = [data]; + } + + if (data.length === 0) { + return isText ? ( + "No data" + ) : ( + + ); + } + + return isText ? data.join(", ") : renderChipList(data); + } + //handle assignedUsers if (cellName === "AssignedUsers" || cellName === "assignedUsers") { //show the display name in text. otherwise, just return the obj. From e008368171c5b126bfb916c8ac2327e18e30e32e Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 17 Mar 2026 17:06:54 -0400 Subject: [PATCH 26/29] fix: adjust dialog fullScreen behavior and height for non-mdDown screens --- src/components/CippWizard/CippWizardPage.jsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/components/CippWizard/CippWizardPage.jsx b/src/components/CippWizard/CippWizardPage.jsx index f21b6876f01f..cda2b0cf5c58 100644 --- a/src/components/CippWizard/CippWizardPage.jsx +++ b/src/components/CippWizard/CippWizardPage.jsx @@ -58,8 +58,14 @@ const CippWizardPage = (props) => { onClose={onClose} fullWidth maxWidth="xl" - fullScreen={other?.fullScreen || mdDown} - PaperProps={{ sx: { display: "flex", flexDirection: "column" } }} + fullScreen={mdDown} + PaperProps={{ + sx: { + display: "flex", + flexDirection: "column", + ...(!mdDown && { height: "90vh" }), + }, + }} > {dialogIcon} From 1f6b185b1689a04999f52d4958807b8c4c1b6ac8 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 17 Mar 2026 17:09:22 -0400 Subject: [PATCH 27/29] remove fullscreen prop --- src/pages/identity/administration/offboarding-wizard/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/identity/administration/offboarding-wizard/index.js b/src/pages/identity/administration/offboarding-wizard/index.js index 496829ca670e..8fd198dd47b4 100644 --- a/src/pages/identity/administration/offboarding-wizard/index.js +++ b/src/pages/identity/administration/offboarding-wizard/index.js @@ -169,7 +169,6 @@ const Page = () => { steps={steps} postUrl="/api/ExecOffboardUser" wizardTitle="User Offboarding Wizard" - fullScreen /> ); From 8ac1658a5696ce515507a2965cb30fff8f2a64df Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 17 Mar 2026 17:25:45 -0400 Subject: [PATCH 28/29] feat: allow scheduled task actions to be limited by label --- src/components/CippComponents/CippScheduledTaskActions.jsx | 6 ++---- .../identity/administration/offboarding-wizard/index.js | 4 +++- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/CippComponents/CippScheduledTaskActions.jsx b/src/components/CippComponents/CippScheduledTaskActions.jsx index 2df628a81cdb..1e37c3b151b1 100644 --- a/src/components/CippComponents/CippScheduledTaskActions.jsx +++ b/src/components/CippComponents/CippScheduledTaskActions.jsx @@ -2,7 +2,7 @@ import { EyeIcon, TrashIcon } from "@heroicons/react/24/outline"; import { CopyAll, Edit, PlayArrow } from "@mui/icons-material"; import { usePermissions } from "../../hooks/use-permissions"; -export const CippScheduledTaskActions = (drawerHandlers = {}) => { +export const CippScheduledTaskActions = (drawerHandlers = {}, { hideActions = [] } = {}) => { const { checkPermissions } = usePermissions(); const canWriteScheduler = checkPermissions(["CIPP.Scheduler.ReadWrite"]); const canReadScheduler = checkPermissions(["CIPP.Scheduler.Read", "CIPP.Scheduler.ReadWrite"]); @@ -29,7 +29,6 @@ export const CippScheduledTaskActions = (drawerHandlers = {}) => { customFunction: drawerHandlers.openEditDrawer || ((row) => { - // Fallback to page navigation if no drawer handler provided window.location.href = `/cipp/scheduler/job?id=${row.RowKey}`; }), multiPost: false, @@ -44,7 +43,6 @@ export const CippScheduledTaskActions = (drawerHandlers = {}) => { customFunction: drawerHandlers.openCloneDrawer || ((row) => { - // Fallback to page navigation if no drawer handler provided window.location.href = `/cipp/scheduler/job?id=${row.RowKey}&Clone=True`; }), multiPost: false, @@ -64,7 +62,7 @@ export const CippScheduledTaskActions = (drawerHandlers = {}) => { multiPost: false, condition: () => canWriteScheduler, }, - ]; + ].filter((action) => !hideActions.includes(action.label)); }; export default CippScheduledTaskActions; diff --git a/src/pages/identity/administration/offboarding-wizard/index.js b/src/pages/identity/administration/offboarding-wizard/index.js index 8fd198dd47b4..6bf7ec6ff0c8 100644 --- a/src/pages/identity/administration/offboarding-wizard/index.js +++ b/src/pages/identity/administration/offboarding-wizard/index.js @@ -25,7 +25,9 @@ const Page = () => { openCloneDrawer: (row) => setCloneTaskId(row.RowKey), }; - const actions = CippScheduledTaskActions(drawerHandlers); + const actions = CippScheduledTaskActions(drawerHandlers, { + hideActions: ["Edit Job", "Clone Job"], + }); const steps = [ { From 320ac2880f3c16542dc27eb38f0cd5772df69bb7 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 17 Mar 2026 18:13:14 -0400 Subject: [PATCH 29/29] chore: update version to 10.2.4 in package.json and version.json --- package.json | 2 +- public/version.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 380e9983f020..295eea0297e5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cipp", - "version": "10.2.2", + "version": "10.2.4", "author": "CIPP Contributors", "homepage": "https://cipp.app/", "bugs": { diff --git a/public/version.json b/public/version.json index 2550102c4f0b..0e0e9967ccb6 100644 --- a/public/version.json +++ b/public/version.json @@ -1,3 +1,3 @@ { - "version": "10.2.2" + "version": "10.2.4" } \ No newline at end of file