diff --git a/src/components/CippComponents/CippApiDialog.jsx b/src/components/CippComponents/CippApiDialog.jsx index bd6348868c8b..99c5bcc455c9 100644 --- a/src/components/CippComponents/CippApiDialog.jsx +++ b/src/components/CippComponents/CippApiDialog.jsx @@ -207,6 +207,40 @@ export const CippApiDialog = (props) => { const onSubmit = (data) => handleActionClick(row, api, data); const selectedType = api.type === "POST" ? actionPostRequest : actionGetRequest; + if (api?.setDefaultValues) { + fields.map((field) => { + if ( + ((typeof row[field.name] === "string" && field.type === "textField") || + (typeof row[field.name] === "boolean" && field.type === "switch")) && + row[field.name] !== undefined && + row[field.name] !== null && + row[field.name] !== "" + ) { + formHook.setValue(field.name, row[field.name]); + } else if (Array.isArray(row[field.name]) && field.type === "autoComplete") { + var values = []; + row[field.name].map((element) => { + values.push({ + label: element, + value: element, + }); + }); + formHook.setValue(field.name, values); + } else if ( + field.type === "autoComplete" && + row[field.name] !== undefined && + row[field.name] !== null && + row[field.name] !== "" && + typeof row[field.name] === "string" + ) { + formHook.setValue(field.name, { + label: row[field.name], + value: row[field.name], + }); + } + }); + } + // Handling link navigation if (api.link) { const getNestedValue = (obj, path) => { diff --git a/src/components/CippComponents/CippTranslations.jsx b/src/components/CippComponents/CippTranslations.jsx index 0876bded5bd2..7761362d09ed 100644 --- a/src/components/CippComponents/CippTranslations.jsx +++ b/src/components/CippComponents/CippTranslations.jsx @@ -44,4 +44,5 @@ export const CippTranslations = { "commitmentTerm.renewalConfiguration.renewalDate": "Renewal Date", storageUsedInBytes: "Storage Used", prohibitSendReceiveQuotaInBytes: "Quota", + ClientId: "Client ID", }; diff --git a/src/components/CippIntegrations/CippApiClientManagement.jsx b/src/components/CippIntegrations/CippApiClientManagement.jsx new file mode 100644 index 000000000000..01e174c64e46 --- /dev/null +++ b/src/components/CippIntegrations/CippApiClientManagement.jsx @@ -0,0 +1,354 @@ +import { Button, Stack, SvgIcon, Menu, MenuItem, ListItemText } from "@mui/material"; +import { useState } from "react"; +import { useForm } from "react-hook-form"; +import { ApiGetCall, ApiPostCall } from "/src/api/ApiCall"; +import { CippDataTable } from "../CippTable/CippDataTable"; +import { + ChevronDownIcon, + ClipboardDocumentIcon, + PencilIcon, + PlusSmallIcon, + TrashIcon, +} from "@heroicons/react/24/outline"; +import { CippApiResults } from "../CippComponents/CippApiResults"; +import { CippApiDialog } from "../CippComponents/CippApiDialog"; +import { Create, Key, Save, Sync } from "@mui/icons-material"; +import { CippPropertyListCard } from "../CippCards/CippPropertyListCard"; +import { CippCopyToClipBoard } from "../CippComponents/CippCopyToClipboard"; + +const CippApiClientManagement = () => { + const [openAddClientDialog, setOpenAddClientDialog] = useState(false); + const [openAddExistingAppDialog, setOpenAddExistingAppDialog] = useState(false); + const [menuAnchorEl, setMenuAnchorEl] = useState(null); + + const formControl = useForm({ + mode: "onChange", + }); + + const postCall = ApiPostCall({ + datafromUrl: true, + relatedQueryKeys: ["ApiClients", "AzureConfiguration"], + }); + + const azureConfig = ApiGetCall({ + url: "/api/ExecApiClient", + data: { Action: "GetAzureConfiguration" }, + queryKey: "AzureConfiguration", + }); + + const handleMenuOpen = (event) => { + setMenuAnchorEl(event.currentTarget); + }; + + const handleMenuClose = () => { + setMenuAnchorEl(null); + }; + + const handleSaveToAzure = () => { + postCall.mutate({ + url: `/api/ExecApiClient?action=SaveToAzure`, + data: {}, + }); + handleMenuClose(); + }; + + const actions = [ + { + label: "Edit", + icon: ( + + + + ), + confirmText: "Update the API client settings:", + hideBulk: true, + setDefaultValues: true, + fields: [ + { + type: "autoComplete", + name: "Role", + multiple: false, + creatable: false, + placeholder: "Select Role", + api: { + url: "/api/ListCustomRole", + queryKey: "CustomRoleList", + labelField: "RowKey", + valueField: "RowKey", + }, + }, + { + type: "autoComplete", + name: "IpRange", + multiple: true, + freeSolo: true, + creatable: true, + options: [], + placeholder: "Enter IP Range (Single hosts or CIDR notation)", + }, + { + type: "switch", + name: "Enabled", + label: "Enable this client", + }, + ], + type: "POST", + url: "/api/ExecApiClient", + data: { + Action: "AddUpdate", + ClientId: "ClientId", + }, + relatedQueryKeys: ["ApiClients"], + }, + { + label: "Reset Application Secret", + icon: , + confirmText: "Are you sure you want to reset the application secret?", + type: "POST", + url: "/api/ExecApiClient", + data: { + Action: "ResetSecret", + ClientId: "ClientId", + }, + }, + { + label: "Copy API Scope", + icon: , + noConfirm: true, + customFunction: (row, action, formData) => { + var scope = `api://${row.ClientId}/.default`; + navigator.clipboard.writeText(scope); + }, + }, + { + label: "Delete Client", + icon: , + confirmText: "Are you sure you want to delete this client?", + type: "POST", + url: "/api/ExecApiClient", + data: { + Action: "Delete", + ClientId: "ClientId", + }, + fields: [ + { + type: "switch", + name: "RemoveAppReg", + label: "Remove App Registration", + }, + ], + relatedQueryKeys: ["ApiClients"], + }, + ]; + + return ( + <> + + + + + { + handleMenuClose(); + setOpenAddClientDialog(true); + }} + > + + + + Create New Client + + { + handleMenuClose(); + setOpenAddExistingAppDialog(true); + }} + > + + + + Add Existing Client + + azureConfig.refetch()}> + + + + Refresh Configuration + + + + + + Save to Azure + + + + } + propertyItems={[ + { label: "API Auth Enabled", value: azureConfig.data?.Results?.Enabled }, + { + label: "API Url", + value: azureConfig.data?.Results?.ApiUrl ? ( + + ) : ( + "Not Available" + ), + }, + { + label: "Token URL", + value: azureConfig.data?.Results?.TenantID ? ( + + ) : ( + "Not Available" + ), + }, + ]} + layout="dual" + showDivider={false} + isFetching={azureConfig.isFetching} + /> + + + + + + setOpenAddClientDialog(false), + }} + title="Add Client" + fields={[ + { + type: "textField", + name: "AppName", + placeholder: "Enter App Name", + }, + { + type: "autoComplete", + name: "Role", + multiple: false, + creatable: false, + placeholder: "Select Role", + api: { + url: "/api/ListCustomRole", + queryKey: "CustomRoleList", + labelField: "RowKey", + valueField: "RowKey", + }, + }, + { + type: "autoComplete", + name: "IpRange", + multiple: true, + freeSolo: true, + creatable: true, + options: [], + placeholder: "Enter IP Range (Single hosts or CIDR notation)", + }, + { + type: "switch", + name: "Enabled", + label: "Enable this client", + }, + ]} + api={{ + type: "POST", + url: "/api/ExecApiClient", + data: { Action: "AddUpdate" }, + relatedQueryKeys: [`ApiClients`], + }} + /> + setOpenAddExistingAppDialog(false), + }} + title="Add Existing App" + fields={[ + { + type: "autoComplete", + name: "ClientId", + placeholder: "Select Existing App", + api: { + type: "GET", + url: "/api/ExecApiClient", + data: { Action: "ListAvailable" }, + queryKey: `AvailableApiApps`, + dataKey: "Results", + labelField: (app) => `${app.displayName} (${app.appId})`, + valueField: "appId", + addedField: { + displayName: "displayName", + createdDateTime: "createdDateTime", + }, + }, + creatable: false, + multiple: false, + }, + { + type: "autoComplete", + name: "Role", + multiple: false, + creatable: false, + placeholder: "Select Role", + api: { + url: "/api/ListCustomRole", + queryKey: "CustomRoleList", + labelField: "RowKey", + valueField: "RowKey", + }, + }, + { + type: "autoComplete", + name: "IpRange", + multiple: true, + freeSolo: true, + creatable: true, + options: [], + placeholder: "Enter IP Range(s)", + }, + { + type: "switch", + name: "Enabled", + label: "Enable this client", + }, + ]} + api={{ + type: "POST", + url: "/api/ExecApiClient", + data: { Action: "!AddUpdate" }, + relatedQueryKeys: [`ApiClients`], + }} + /> + + ); +}; + +export default CippApiClientManagement; diff --git a/src/data/Extensions.json b/src/data/Extensions.json index 0110e7d63729..e81d25cf3468 100644 --- a/src/data/Extensions.json +++ b/src/data/Extensions.json @@ -7,19 +7,12 @@ "logo": "/assets/integrations/cipp-api.png", "forceSyncButton": false, "hideTestButton": true, - "disableWhenhosted": true, - "description": "Enable the CIPP API to access CIPP data outside of CIPP using a RESTful API.", - "helpText": "This integration allows you to enable CIPP-API access outside of CIPP. Requires Global Administrator permissions inside your tenant for activation of the API. The API credentials will only be shown once.", - "SettingOptions": [ - { - "type": "switch", - "name": "cippapi.ResetPassword", - "label": "Reset application secret - this will invalidate all existing API tokens" - }, + "description": "The CIPP API allows you to access CIPP data outside of CIPP using a RESTful API.", + "helpText": "This integration allows you to enable CIPP-API access outside of CIPP. This is useful for integrations with other systems or custom scripts.", + "links": [ { - "type": "switch", - "name": "cippapi.Enabled", - "label": "Enable Integration" + "name": "CIPP API Documentation", + "url": "https://docs.cipp.app/api-documentation/setup-and-authentication" } ], "mappingRequired": false @@ -76,7 +69,7 @@ "name": "Sherweb.AllowedCustomRoles", "label": "Select custom roles that are allowed to purchase licenses", "api": { - "url": "/api/ExecCustomRole", + "url": "/api/ListCustomRole", "queryKey": "CustomRoles", "labelField": "RowKey", "valueField": "RowKey" @@ -463,5 +456,36 @@ } ], "mappingRequired": false + }, + { + "name": "GitHub", + "id": "GitHub", + "type": "DevOps", + "cat": "Source Control", + "logo": "/assets/integrations/github.png", + "logoDark": "/assets/integrations/github_dark.png", + "description": "Enable the GitHub integration to manage your repositories from CIPP.", + "helpText": "This integration allows you to manage your GitHub repositories from CIPP. Requires a GitHub Fine Grained Personal Access Token (PAT).", + "links": [ + { + "name": "GitHub Token", + "url": "https://docs.github.com/en/github/authenticating-to-github/keeping-your-account-and-data-secure/creating-a-personal-access-token" + } + ], + "SettingOptions": [ + { + "type": "password", + "name": "GitHub.APIKey", + "label": "GitHub Fine Grained Personal Access Token", + "placeholder": "Enter your GitHub PAT", + "required": true + }, + { + "type": "switch", + "name": "GitHub.Enabled", + "label": "Enable Integration" + } + ], + "mappingRequired": false } ] diff --git a/src/pages/cipp/integrations/configure.js b/src/pages/cipp/integrations/configure.js index e45bf0fd4489..1995566ddd38 100644 --- a/src/pages/cipp/integrations/configure.js +++ b/src/pages/cipp/integrations/configure.js @@ -15,6 +15,7 @@ import CippPageCard from "../../../components/CippCards/CippPageCard"; import CippIntegrationTenantMapping from "../../../components/CippIntegrations/CippIntegrationTenantMapping"; import CippIntegrationFieldMapping from "../../../components/CippIntegrations/CippIntegrationFieldMapping"; import { CippCardTabPanel } from "../../../components/CippComponents/CippCardTabPanel"; +import CippApiClientManagement from "../../../components/CippIntegrations/CippApiClientManagement"; function tabProps(index) { return { @@ -77,7 +78,7 @@ const Page = () => { defaultValues: integrations?.data, }); - const extension = extensions.find((extension) => extension.id === router.query.id); + const extension = extensions.find((extension) => extension.id === router.query.id) || {}; var logo = extension?.logo; if (preferredTheme === "dark" && extension?.logoDark) { @@ -185,12 +186,16 @@ const Page = () => { {extension?.mappingRequired && } {extension?.fieldMapping && } - {extension?.id === "cippapi" && } - + {extension?.id === "cippapi" ? ( + + ) : ( + + )} + {extension?.mappingRequired && ( @@ -201,11 +206,6 @@ const Page = () => { )} - {extension?.id === "cippapi" && ( - - API Client component to go here - - )} )} diff --git a/src/pages/cipp/integrations/index.js b/src/pages/cipp/integrations/index.js index 241ced5c4792..4bf582e23432 100644 --- a/src/pages/cipp/integrations/index.js +++ b/src/pages/cipp/integrations/index.js @@ -56,11 +56,11 @@ const Page = () => { } var integrationConfig = integrations?.data?.[extension.id]; - var isEnabled = integrationConfig?.Enabled; + var isEnabled = integrationConfig?.Enabled || extension.id === "cippapi"; var status = "Unconfigured"; if (integrationConfig && !isEnabled) { status = "Disabled"; - } else if (integrationConfig && isEnabled) { + } else if ((integrationConfig && isEnabled) || extension.id === "cippapi") { status = "Enabled"; } diff --git a/src/utils/get-cipp-formatting.js b/src/utils/get-cipp-formatting.js index 73c65b8dbdad..2feaacacf94d 100644 --- a/src/utils/get-cipp-formatting.js +++ b/src/utils/get-cipp-formatting.js @@ -219,6 +219,10 @@ export const getCippFormatting = (data, cellName, type, canReceive) => { } } + if (cellName === "ClientId") { + return isText ? data : ; + } + if (cellName === "excludedTenants") { //check if data is an array. if (Array.isArray(data)) {