Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
{
"name": "cipp",
"version": "10.9.1",
"version": "10.10.1",
"author": "CIPP Contributors",
"homepage": "https://cipp.app/",
"bugs": {
"url": "https://github.com/CyberDrain/CIPP/issues"
},
"license": "AGPL-3.0",
"engines": {
"node": "^22.22.2"
"node": "^22.22.0"
},
"repository": {
"type": "git",
Expand Down Expand Up @@ -38,7 +38,7 @@
"@emotion/react": "11.14.0",
"@emotion/server": "11.11.0",
"@emotion/styled": "11.14.1",
"@floating-ui/dom": "1.7.6",
"@floating-ui/dom": "1.8.0",
"@heroicons/react": "2.2.0",
"@monaco-editor/react": "^4.6.0",
"@mui/icons-material": "9.4.0",
Expand All @@ -64,7 +64,7 @@
"@tiptap/react": "^3.31.3",
"@tiptap/starter-kit": "^3.31.3",
"@vvo/tzdb": "^6.198.0",
"apexcharts": "6.6.1",
"apexcharts": "7.1.0",
"axios": "1.18.1",
"date-fns": "4.4.0",
"diff": "^9.0.0",
Expand Down Expand Up @@ -123,7 +123,7 @@
},
"devDependencies": {
"@storybook/addon-a11y": "10.3.5",
"@storybook/addon-docs": "10.3.5",
"@storybook/addon-docs": "10.5.10",
"@storybook/addon-themes": "10.3.5",
"@storybook/addon-vitest": "10.3.5",
"@storybook/react-vite": "10.3.5",
Expand All @@ -137,12 +137,12 @@
"@vitest/browser-playwright": "4.1.10",
"@vitest/coverage-v8": "4.1.10",
"eslint": "^9.39.4",
"eslint-config-next": "^16.2.10",
"eslint-config-next": "^16.3.4",
"eslint-config-prettier": "^10.1.8",
"jsdom": "30.0.1",
"jsdom": "29.0.1",
"msw": "2.15.0",
"msw-storybook-addon": "3.0.0",
"playwright": "1.59.1",
"playwright": "1.63.0",
"prettier": "^3.9.6",
"storybook": "10.5.10",
"typescript": "5.9.3",
Expand Down
4 changes: 2 additions & 2 deletions public/version.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"version": "10.10.0"
}
"version": "10.10.1"
}
7 changes: 7 additions & 0 deletions src/components/CippComponents/CippPolicyImportDrawer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,13 @@ const modeConfig = {
tenantSource: false,
relatedQueryKeys: ['ListDlpCompliancePolicyTemplates'],
},
PIMRoleSettings: {
label: 'PIM Role Settings',
types: ['PIMRoleSettingsTemplate'],
tenantSource: false,
// The list queryKey is tenant-suffixed (ListPIMRoleSettingsTemplates-<tenant>), so match with a wildcard.
relatedQueryKeys: ['ListPIMRoleSettingsTemplates*'],
},
}

export const CippPolicyImportDrawer = ({
Expand Down
31 changes: 30 additions & 1 deletion src/components/CippComponents/CippUserActions.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -867,6 +867,9 @@ export const useCippUserActions = () => {
username: 'userPrincipalName',
userid: 'id',
},
defaultvalues: {
destination: { label: 'OneDrive root', value: 'root' },
},
fields: [
{
type: 'autoComplete',
Expand All @@ -883,8 +886,34 @@ export const useCippUserActions = () => {
queryKey: `sharepointSites-${tenant}`,
},
},
{
type: 'autoComplete',
name: 'destination',
label: 'Shortcut location',
multiple: false,
creatable: false,
options: [
{ label: 'OneDrive root', value: 'root' },
{ label: 'Shortcuts folder (Microsoft UI)', value: 'shortcuts' },
],
validators: { required: 'Please select a shortcut location' },
},
],
confirmText: 'Select a SharePoint site to create a shortcut for:',
confirmText: 'Select a SharePoint site and where to create the OneDrive shortcut:',
multiPost: false,
condition: () => canWriteUser,
},
{
label: 'Migrate OneDrive Shortcuts',
type: 'POST',
icon: <CippIcons.Shortcut />,
url: '/api/ExecMigrateOneDriveShortCuts',
data: {
username: 'userPrincipalName',
userid: 'id',
},
confirmText:
'Migrate root OneDrive shortcuts for [userPrincipalName] into the Shortcuts folder?',
multiPost: false,
condition: () => canWriteUser,
},
Expand Down
23 changes: 23 additions & 0 deletions src/data/standards.json
Original file line number Diff line number Diff line change
Expand Up @@ -5395,6 +5395,29 @@
"ONEDRIVEENTERPRISE"
]
},
{
"name": "standards.MigrateOneDriveShortcuts",
"cat": "SharePoint Standards",
"tag": [],
"helpText": "Finds SharePoint library shortcuts sitting in each user's OneDrive root and moves them into the Shortcuts folder (PATCH move into special/shortcuts), matching the optional Microsoft UI location.",
"docsDescription": "Over time Add shortcut to OneDrive can leave many remote library links in the OneDrive root. Microsoft also supports placing those links in an optional Shortcuts folder. This standard lists each enabled member user's OneDrive root with Prefer Include-Feature=AddToOneDrive, then for any remoteItem shortcuts still outside Shortcuts moves them into special/shortcuts. Users without a provisioned OneDrive are skipped. Failures name the user, shortcut, and site URL when available.",
"executiveText": "Keeps employee OneDrive roots tidy by moving SharePoint library shortcuts into the dedicated Shortcuts folder instead of leaving them scattered among personal files.",
"addedComponent": [],
"label": "Migrate OneDrive root shortcuts to the Shortcuts folder",
"impact": "Low Impact",
"impactColour": "info",
"addedDate": "2026-09-08",
"powershellEquivalent": "PATCH drive/items/{id} parentReference → special/shortcuts",
"recommendedBy": [],
"requiredCapabilities": [
"SHAREPOINTWAC",
"SHAREPOINTSTANDARD",
"SHAREPOINTENTERPRISE",
"SHAREPOINTENTERPRISE_EDU",
"SHAREPOINTENTERPRISE_GOV",
"ONEDRIVEENTERPRISE"
]
},
{
"name": "standards.SPFileRequests",
"cat": "SharePoint Standards",
Expand Down
80 changes: 80 additions & 0 deletions src/pages/cipp/advanced/container-management/worker-health.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,71 @@ const CompactStatsRow = ({ snapshot }) => {
);
};

// OS-thread breakdown from the MemoryDetail bridge — a point-in-time read of how many
// threads the process holds and what they are doing. The PowerShell worker pool
// (HTTP + BG) dominates the count and stays bounded, so ThreadCount next to TotalWorkers
// is the direct "nothing is leaking threads" read for operators.
const ThreadStatsCard = ({ detail }) => {
if (!detail) return null;

const states = detail.ThreadStates || {};
const stateEntries = Object.entries(states).sort((a, b) => b[1] - a[1]);

const poolStats = [
{ k: "Processors", v: detail.ProcessorCount ?? 0 },
{ k: "HTTP Workers", v: detail.HttpWorkers ?? 0 },
{ k: "BG Workers", v: detail.BgWorkers ?? 0 },
{ k: "Total Workers", v: detail.TotalWorkers ?? 0 },
];

return (
<Card>
<CardHeader
title="Threads"
avatar={<CippIcons.AccountTree color="primary" />}
subheader={`${detail.ThreadCount ?? 0} OS threads • ${detail.TotalWorkers ?? 0} pooled workers across ${detail.ProcessorCount ?? 0} processors`}
action={<Chip label={`${detail.ThreadCount ?? 0} threads`} color="primary" size="small" />}
slotProps={{
title: { variant: "h6" },
subheader: { variant: "caption" },
}}
/>
<CardContent sx={{ pt: 0 }}>
<Stack
useFlexGap
direction="row"
sx={{ flexWrap: "wrap", gap: 2, rowGap: 1, mb: stateEntries.length > 0 ? 2 : 0 }}
>
{poolStats.map((s) => (
<StatPair key={s.k} label={s.k} value={s.v} />
))}
</Stack>
{stateEntries.length > 0 && (
<>
<Typography
variant="caption"
sx={{ color: "text.secondary", display: "block", mb: 0.75 }}
>
Thread states
</Typography>
<Stack useFlexGap direction="row" sx={{ flexWrap: "wrap", gap: 1 }}>
{stateEntries.map(([state, count]) => (
<Chip
key={state}
label={`${state}: ${count}`}
size="small"
variant="outlined"
color={state === "Running" ? "success" : "default"}
/>
))}
</Stack>
</>
)}
</CardContent>
</Card>
);
};

// CippInfoBar renders its value slot nowrap + ellipsis; a block child re-enables wrapping
// so long memory/CPU strings stay whole instead of truncating mid-number.
const WrappedStat = ({ children }) => (
Expand Down Expand Up @@ -641,9 +706,19 @@ const Page = () => {
refetchInterval: effectivePaused ? false : 30000,
});

// MemoryDetail is heavier than the 5s snapshot (it enumerates threads + assemblies), so
// poll it on the slower 30s cadence — the thread count it carries is a stable diagnostic.
const memoryDetailQuery = ApiGetCall({
url: "/api/ListWorkerHealth",
data: { Action: "MemoryDetail" },
queryKey: "WorkerMemoryDetail",
refetchInterval: effectivePaused ? false : 30000,
});

// Resolve data: imported overrides live
const snapshot = isImported ? importedData.snapshot : healthQuery.data?.Results;
const startupInfo = isImported ? importedData.startup : startupQuery.data?.Results;
const memoryDetail = isImported ? importedData.memoryDetail : memoryDetailQuery.data?.Results;
const importedJobs = useMemo(() => {
if (!isImported || !importedData.jobs) return null;
// Handle both array and { Results: [...] } shapes from query cache
Expand Down Expand Up @@ -673,6 +748,7 @@ const Page = () => {
snapshot: healthQuery.data?.Results ?? null,
startup: startupQuery.data?.Results ?? null,
history: historyQuery.data?.Results ?? null,
memoryDetail: memoryDetailQuery.data?.Results ?? null,
jobs: null,
};
// Grab the job data for the currently selected limit/status only — a prefix match
Expand All @@ -697,6 +773,7 @@ const Page = () => {
healthQuery.data,
startupQuery.data,
historyQuery.data,
memoryDetailQuery.data,
historyRange,
queryClient,
jobLimit,
Expand Down Expand Up @@ -933,6 +1010,9 @@ const Page = () => {
{/* ── Compact pool / jobs / limiter stats ── */}
<CompactStatsRow snapshot={snapshot} />

{/* ── OS thread breakdown ── */}
<ThreadStatsCard detail={memoryDetail} />

{/* ── Worker tables ── */}
<WorkerTable workers={snapshot?.HttpPool?.Workers} title="HTTP Workers" />
<WorkerTable workers={snapshot?.BgPool?.Workers} title="Background Workers" />
Expand Down
24 changes: 17 additions & 7 deletions src/pages/identity/administration/roles/templates/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import { ApiGetCall } from '../../../../../api/ApiCall'
import { useSettings } from '../../../../../hooks/use-settings'
import { CippTablePage } from '../../../../../components/CippComponents/CippTablePage.jsx'
import { CippPropertyListCard } from '../../../../../components/CippCards/CippPropertyListCard'
import { CippPolicyImportDrawer } from '../../../../../components/CippComponents/CippPolicyImportDrawer.jsx'
import { Layout as DashboardLayout } from '../../../../../layouts/index'
import { TabbedLayout } from '../../../../../layouts/TabbedLayout'
import { getCippTranslation } from '../../../../../utils/get-cipp-translation'
import { getCippFormatting } from '../../../../../utils/get-cipp-formatting'
import { PermissionButton } from '../../../../../utils/permissions'
import tabOptions from '../tabOptions.json'

const Page = () => {
Expand Down Expand Up @@ -120,13 +122,21 @@ const Page = () => {
tenantInTitle={false}
actions={actions}
cardButton={
<Button
component={Link}
href="/identity/administration/roles/templates/add"
startIcon={<CippIcons.AddBox />}
>
Add PIM Template
</Button>
<>
<Button
component={Link}
href="/identity/administration/roles/templates/add"
startIcon={<CippIcons.AddBox />}
>
Add PIM Template
</Button>
<CippPolicyImportDrawer
buttonText="Browse Catalog"
requiredPermissions={['Identity.Role.ReadWrite']}
PermissionButton={PermissionButton}
mode="PIMRoleSettings"
/>
</>
}
offCanvas={offCanvas}
simpleColumns={[
Expand Down
Loading
Loading