Merge branch 'main' of https://github.com/Infisical/infisical into feat/pagination-in-resource-overview-tables
@@ -75,7 +75,7 @@ class RailwayPublicClient {
|
||||
async send<T extends TRailwayResponse>(
|
||||
query: string,
|
||||
options: RailwaySendReqOptions,
|
||||
variables: Record<string, string | Record<string, string>> = {},
|
||||
variables: Record<string, unknown> = {},
|
||||
retryAttempt: number = 0
|
||||
): Promise<T["data"] | undefined> {
|
||||
const body = {
|
||||
@@ -117,6 +117,25 @@ class RailwayPublicClient {
|
||||
}
|
||||
}
|
||||
|
||||
async getDeployments(
|
||||
config: RailwaySendReqOptions,
|
||||
variables: { input: { serviceId: string; environmentId: string }; first?: number }
|
||||
) {
|
||||
return this.send<TRailwayResponse<{ deployments: { edges: { node: { id: string } }[] } }>>(
|
||||
`query deployments($input: DeploymentListInput!, $first: Int) { deployments(first: $first, input: $input) { edges { node { id } } } }`,
|
||||
config,
|
||||
variables
|
||||
);
|
||||
}
|
||||
|
||||
async redeployDeployment(config: RailwaySendReqOptions, variables: { input: { deploymentId: string } }) {
|
||||
return this.send<TRailwayResponse<{ deploymentRedeploy: { id: string } }>>(
|
||||
`mutation deploymentRedeploy($deploymentId: String!) { deploymentRedeploy(id: $deploymentId) { id } }`,
|
||||
config,
|
||||
{ deploymentId: variables.input.deploymentId }
|
||||
);
|
||||
}
|
||||
|
||||
async getSubscriptionType(config: RailwaySendReqOptions & { projectId: string }) {
|
||||
const res = await this.send(
|
||||
`query project($projectId: String!) { project(id: $projectId) { subscriptionType }}`,
|
||||
@@ -213,7 +232,9 @@ class RailwayPublicClient {
|
||||
|
||||
async deleteVariable(
|
||||
config: RailwaySendReqOptions,
|
||||
variables: { input: { projectId: string; environmentId: string; name: string; serviceId?: string } }
|
||||
variables: {
|
||||
input: { projectId: string; environmentId: string; name: string; skipDeploys?: boolean; serviceId?: string };
|
||||
}
|
||||
) {
|
||||
await this.send<TRailwayResponse<{ variables: Record<string, string> }>>(
|
||||
`mutation variableDelete($input: VariableDeleteInput!) { variableDelete(input: $input) }`,
|
||||
@@ -222,6 +243,26 @@ class RailwayPublicClient {
|
||||
);
|
||||
}
|
||||
|
||||
async upsertCollection(
|
||||
config: RailwaySendReqOptions,
|
||||
variables: {
|
||||
input: {
|
||||
projectId: string;
|
||||
environmentId: string;
|
||||
variables: Record<string, string>;
|
||||
skipDeploys?: boolean;
|
||||
serviceId?: string;
|
||||
replace?: boolean;
|
||||
};
|
||||
}
|
||||
) {
|
||||
return this.send<TRailwayResponse<boolean>>(
|
||||
`mutation variableCollectionUpsert($input: VariableCollectionUpsertInput!) { variableCollectionUpsert(input: $input) }`,
|
||||
config,
|
||||
variables
|
||||
);
|
||||
}
|
||||
|
||||
async upsertVariable(
|
||||
config: RailwaySendReqOptions,
|
||||
variables: { input: { projectId: string; environmentId: string; name: string; value: string; serviceId?: string } }
|
||||
|
||||
@@ -12,6 +12,8 @@ export const RailwaySyncFns = {
|
||||
async getSecrets(secretSync: TRailwaySyncWithCredentials): Promise<TSecretMap> {
|
||||
try {
|
||||
const config = secretSync.destinationConfig;
|
||||
const { keySchema } = secretSync.syncOptions;
|
||||
const { environment } = secretSync;
|
||||
|
||||
const variables = await RailwayPublicAPI.getVariables(secretSync.connection, {
|
||||
projectId: config.projectId,
|
||||
@@ -26,6 +28,10 @@ export const RailwaySyncFns = {
|
||||
// eslint-disable-next-line no-continue
|
||||
if (key.startsWith("RAILWAY_")) continue;
|
||||
|
||||
// Check if key matches the schema
|
||||
// eslint-disable-next-line no-continue
|
||||
if (!matchesSchema(key, environment?.slug || "", keySchema)) continue;
|
||||
|
||||
entries[key] = {
|
||||
value
|
||||
};
|
||||
@@ -40,60 +46,73 @@ export const RailwaySyncFns = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Syncs secrets to Railway and redeploys the service if needed.
|
||||
*
|
||||
* Gets existing Railway vars, merges with new secrets (keeping Railway vars if deletion is disabled),
|
||||
* then replaces every variable with the new values, if variable is not in the secretMap, it is deleted.
|
||||
* If there's a service, triggers a redeploy to pick up the changes.
|
||||
*/
|
||||
async syncSecrets(secretSync: TRailwaySyncWithCredentials, secretMap: TSecretMap) {
|
||||
const {
|
||||
environment,
|
||||
syncOptions: { disableSecretDeletion, keySchema }
|
||||
} = secretSync;
|
||||
const railwaySecrets = await this.getSecrets(secretSync);
|
||||
const config = secretSync.destinationConfig;
|
||||
try {
|
||||
const {
|
||||
syncOptions: { disableSecretDeletion }
|
||||
} = secretSync;
|
||||
const railwaySecrets = await this.getSecrets(secretSync);
|
||||
const config = secretSync.destinationConfig;
|
||||
|
||||
for await (const key of Object.keys(secretMap)) {
|
||||
try {
|
||||
const existing = railwaySecrets[key];
|
||||
const railwaySecretsMap = Object.fromEntries(
|
||||
Object.entries(railwaySecrets).map(([key, secret]) => [key, secret.value])
|
||||
);
|
||||
const secretMapMap = Object.fromEntries(Object.entries(secretMap).map(([key, secret]) => [key, secret.value]));
|
||||
|
||||
if (existing === undefined || existing.value !== secretMap[key].value) {
|
||||
await RailwayPublicAPI.upsertVariable(secretSync.connection, {
|
||||
input: {
|
||||
projectId: config.projectId,
|
||||
environmentId: config.environmentId,
|
||||
serviceId: config.serviceId || undefined,
|
||||
name: key,
|
||||
value: secretMap[key].value ?? ""
|
||||
}
|
||||
});
|
||||
const toReplace = disableSecretDeletion ? { ...railwaySecretsMap, ...secretMapMap } : secretMapMap;
|
||||
|
||||
const upserted = await RailwayPublicAPI.upsertCollection(secretSync.connection, {
|
||||
input: {
|
||||
projectId: config.projectId,
|
||||
environmentId: config.environmentId,
|
||||
serviceId: config.serviceId || undefined,
|
||||
skipDeploys: true,
|
||||
variables: toReplace,
|
||||
replace: true
|
||||
}
|
||||
} catch (error) {
|
||||
});
|
||||
|
||||
if (!upserted)
|
||||
throw new SecretSyncError({
|
||||
error,
|
||||
secretKey: key
|
||||
message: "Failed to upsert secrets to Railway"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (disableSecretDeletion) return;
|
||||
if (!config.serviceId) return;
|
||||
|
||||
for await (const key of Object.keys(railwaySecrets)) {
|
||||
try {
|
||||
// eslint-disable-next-line no-continue
|
||||
if (!matchesSchema(key, environment?.slug || "", keySchema)) continue;
|
||||
const latestDeployment = await RailwayPublicAPI.getDeployments(secretSync.connection, {
|
||||
input: {
|
||||
serviceId: config.serviceId,
|
||||
environmentId: config.environmentId
|
||||
},
|
||||
first: 1
|
||||
});
|
||||
|
||||
if (!secretMap[key]) {
|
||||
await RailwayPublicAPI.deleteVariable(secretSync.connection, {
|
||||
input: {
|
||||
projectId: config.projectId,
|
||||
environmentId: config.environmentId,
|
||||
serviceId: config.serviceId || undefined,
|
||||
name: key
|
||||
}
|
||||
});
|
||||
const latestDeploymentId = latestDeployment?.deployments.edges[0].node.id;
|
||||
|
||||
if (!latestDeploymentId)
|
||||
throw new SecretSyncError({
|
||||
message: "Failed to get latest deployment from Railway"
|
||||
});
|
||||
|
||||
await RailwayPublicAPI.redeployDeployment(secretSync.connection, {
|
||||
input: {
|
||||
deploymentId: latestDeploymentId
|
||||
}
|
||||
} catch (error) {
|
||||
throw new SecretSyncError({
|
||||
error,
|
||||
secretKey: key
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof SecretSyncError) throw error;
|
||||
|
||||
throw new SecretSyncError({
|
||||
error,
|
||||
message: "Failed to sync secrets to Railway"
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@@ -101,24 +120,37 @@ export const RailwaySyncFns = {
|
||||
const existing = await this.getSecrets(secretSync);
|
||||
const config = secretSync.destinationConfig;
|
||||
|
||||
for await (const secret of Object.keys(existing)) {
|
||||
try {
|
||||
if (secret in secretMap) {
|
||||
await RailwayPublicAPI.deleteVariable(secretSync.connection, {
|
||||
input: {
|
||||
projectId: config.projectId,
|
||||
environmentId: config.environmentId,
|
||||
serviceId: config.serviceId || undefined,
|
||||
name: secret
|
||||
}
|
||||
});
|
||||
// Create a new variables object excluding secrets that exist in secretMap
|
||||
const remainingVariables = Object.fromEntries(
|
||||
Object.entries(existing)
|
||||
.filter(([key]) => !(key in secretMap))
|
||||
.map(([key, secret]) => [key, secret.value])
|
||||
);
|
||||
|
||||
try {
|
||||
const upserted = await RailwayPublicAPI.upsertCollection(secretSync.connection, {
|
||||
input: {
|
||||
projectId: config.projectId,
|
||||
environmentId: config.environmentId,
|
||||
serviceId: config.serviceId || undefined,
|
||||
skipDeploys: true,
|
||||
variables: remainingVariables,
|
||||
replace: true
|
||||
}
|
||||
} catch (error) {
|
||||
});
|
||||
|
||||
if (!upserted) {
|
||||
throw new SecretSyncError({
|
||||
error,
|
||||
secretKey: secret
|
||||
message: "Failed to remove secrets from Railway"
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof SecretSyncError) throw error;
|
||||
|
||||
throw new SecretSyncError({
|
||||
error,
|
||||
message: "Failed to remove secrets from Railway"
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -221,6 +221,7 @@
|
||||
]
|
||||
},
|
||||
"documentation/platform/sso/auth0-oidc",
|
||||
"documentation/platform/sso/pingone-oidc",
|
||||
{
|
||||
"group": "General OIDC",
|
||||
"pages": [
|
||||
|
||||
@@ -95,7 +95,7 @@ To successfully deploy an Infisical Gateway for use, follow these steps in order
|
||||
<Step title="Set Up a Relay Server">
|
||||
Ensure a relay server is running and accessible before you deploy any gateways. You have two options:
|
||||
- **Managed relay (Infisical Cloud, US/EU only):** Managed relays are only available for Infisical Cloud instances in the US and EU regions. If you are using Infisical Cloud in these regions, you can use the provided managed relay.
|
||||
- **Self-hosted relay:** For all other cases, including all self-hosted and dedicated enterprise instances of Infisical, you must deploy your own relay server. You can also choose to deploy your own relay server when using Infisical Cloud if you require reduced geographic proximity to your target resources for lower latency or to reduce network congestion. For setup instructions, see the <a href="/documentation/platform/gateways/relay-deployment">Relay Deployment Guide</a>.
|
||||
- **Self-hosted relay:** For all other cases, including all self-hosted and dedicated enterprise instances of Infisical, you must deploy your own relay server. You can also choose to deploy your own relay server when using Infisical Cloud if you require reduced geographic proximity to your target resources for lower latency or to reduce network congestion. For setup instructions, see the [Relay Deployment Guide](/documentation/platform/gateways/relay-deployment).
|
||||
</Step>
|
||||
<Step title="Install the Infisical CLI">
|
||||
Make sure the Infisical CLI is installed on the machine or environment where you plan to deploy the gateway. The CLI is required for gateway installation and management.
|
||||
|
||||
@@ -6,7 +6,7 @@ description: "Learn how to configure Auth0 OIDC for Infisical SSO."
|
||||
<Info>
|
||||
Auth0 OIDC SSO is a paid feature. If you're using Infisical Cloud, then it is
|
||||
available under the **Pro Tier**. If you're self-hosting Infisical, then you
|
||||
should contact sales@infisical.com to purchase an enterprise license to use
|
||||
should contact sales@infisical.com to purchase a self-hosted license to use
|
||||
it.
|
||||
</Info>
|
||||
|
||||
@@ -55,7 +55,7 @@ description: "Learn how to configure Auth0 OIDC for Infisical SSO."
|
||||
<Step title="Enable OIDC in Infisical">
|
||||
Enabling OIDC allows members in your organization to log into Infisical via Auth0.
|
||||
|
||||

|
||||

|
||||
|
||||
</Step>
|
||||
<Step title="Enforce OIDC SSO in Infisical">
|
||||
|
||||
@@ -7,7 +7,7 @@ description: "Learn how to configure OIDC for Infisical SSO with any OIDC-compli
|
||||
<Info>
|
||||
OIDC SSO is a paid feature. If you're using Infisical Cloud, then it is
|
||||
available under the **Pro Tier**. If you're self-hosting Infisical, then you
|
||||
should contact sales@infisical.com to purchase an enterprise license to use
|
||||
should contact sales@infisical.com to purchase a self-hosted license to use
|
||||
it.
|
||||
</Info>
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ description: "Learn how to configure Keycloak OIDC for Infisical SSO."
|
||||
<Info>
|
||||
Keycloak OIDC SSO is a paid feature. If you're using Infisical Cloud, then it
|
||||
is available under the **Pro Tier**. If you're self-hosting Infisical, then
|
||||
you should contact sales@infisical.com to purchase an enterprise license to
|
||||
you should contact sales@infisical.com to purchase a self-hosted license to
|
||||
use it.
|
||||
</Info>
|
||||
|
||||
@@ -82,7 +82,7 @@ description: "Learn how to configure Keycloak OIDC for Infisical SSO."
|
||||
<Step title="Enable OIDC SSO in Infisical">
|
||||
Enabling OIDC SSO allows members in your organization to log into Infisical via Keycloak.
|
||||
|
||||

|
||||

|
||||
|
||||
</Step>
|
||||
<Step title="Enforce OIDC SSO in Infisical">
|
||||
|
||||
108
docs/documentation/platform/sso/pingone-oidc.mdx
Normal file
@@ -0,0 +1,108 @@
|
||||
---
|
||||
title: "PingOne OIDC"
|
||||
description: "Learn how to configure PingOne OIDC for Infisical SSO."
|
||||
---
|
||||
|
||||
<Info>
|
||||
PingOne OIDC SSO is a paid feature. If you're using Infisical Cloud, then it is
|
||||
available under the **Pro Tier**. If you're self-hosting Infisical, then you
|
||||
should contact sales@infisical.com to purchase a self-hosted license to use
|
||||
it.
|
||||
</Info>
|
||||
|
||||
<Steps>
|
||||
<Step title="Setup application in PingOne">
|
||||
1.1. From the Application's Page, create a new OIDC Web App application.
|
||||

|
||||
|
||||
1.2. Enable the application by pressing the "Enable" toggle.
|
||||

|
||||
|
||||
|
||||
1.3. In the Application "Configuration" tab, press the "Edit" pencil icon to configure the application callback URI.
|
||||

|
||||
|
||||
|
||||
1.4 Set the Redirect URL to `https://app.infisical.com/api/v1/sso/oidc/callback` and press the "Save" button.
|
||||

|
||||
|
||||
|
||||
<Info>
|
||||
If you're self-hosting Infisical, then you will want to replace https://app.infisical.com with your own domain.
|
||||
</Info>
|
||||
|
||||
|
||||
1.5 After configuring the redirect URL, go to the "Attribute Mappings" tab and press the "Edit" pencil icon to configure the attribute mappings.
|
||||

|
||||
|
||||
1.6 Map the following attributes:
|
||||
- `email` -> `Email Address`
|
||||
- `name` -> `Username`
|
||||

|
||||
|
||||
Once done, press the "Save" button.
|
||||
|
||||
</Step>
|
||||
<Step title="Retrieve Identity Provider (IdP) Information from PingOne">
|
||||
2.1. Open the "Overview" tab and copy the **Client ID** and **Client Secret**.
|
||||

|
||||
|
||||
2.2. Still in the "Overview" tab, scroll down to the Connection Details section and retrieve the **OIDC Discovery Endpoint**.
|
||||

|
||||
|
||||
Keep these values handy as we will need them in the next steps.
|
||||
|
||||
</Step>
|
||||
<Step title="Finish configuring OIDC in Infisical">
|
||||
3.1. Back in Infisical, head to the **Single Sign-On (SSO)** page and select the **General** tab. Click **Connect** for **OIDC**.
|
||||

|
||||
|
||||
3.2. For configuration type, select **Discovery URL**. Then, set **Discovery Document URL**, **Client ID**, and **Client Secret** from step 2.1 and 2.2.
|
||||
|
||||

|
||||
|
||||
<Info>
|
||||
Currently, the following JWT signature algorithms are supported: RS256, RS512, HS256, and EdDSA
|
||||
</Info>
|
||||
|
||||
Once you've done that, press **Update** to complete the required configuration.
|
||||
|
||||
</Step>
|
||||
<Step title="Enable OIDC in Infisical">
|
||||
Enabling OIDC allows members in your organization to log into Infisical via PingOne
|
||||
|
||||

|
||||
|
||||
</Step>
|
||||
<Step title="Enforce OIDC SSO in Infisical">
|
||||
Enforcing OIDC SSO ensures that members in your organization can only access Infisical
|
||||
by logging into the organization via PingOne.
|
||||
|
||||
To enforce OIDC SSO, you're required to test out the OpenID connection by successfully authenticating at least one PingOne user with Infisical.
|
||||
Once you've completed this requirement, you can toggle the **Enforce OIDC SSO** button to enforce OIDC SSO.
|
||||
|
||||
<Warning>
|
||||
We recommend ensuring that your account is provisioned using the application in PingOne
|
||||
prior to enforcing OIDC SSO to prevent any unintended issues.
|
||||
</Warning>
|
||||
<Info>
|
||||
In case of a lockout, an organization admin can use the [Admin Login Portal](https://infisical.com/docs/documentation/platform/sso/overview#admin-login-portal) in the `/login/admin` path e.g. https://app.infisical.com/login/admin.
|
||||
</Info>
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
<Tip>
|
||||
If you are only using one organization on your Infisical instance, you can configure a default organization in the [Server Admin Console](../admin-panel/server-admin#default-organization) to expedite OIDC login.
|
||||
</Tip>
|
||||
|
||||
<Note>
|
||||
If you're configuring OIDC SSO on a self-hosted instance of Infisical, make
|
||||
sure to set the `AUTH_SECRET` and `SITE_URL` environment variable for it to
|
||||
work:
|
||||
<div class="height:1px;"/>
|
||||
- `AUTH_SECRET`: A secret key used for signing and verifying JWT. This
|
||||
can be a random 32-byte base64 string generated with `openssl rand -base64
|
||||
32`.
|
||||
<div class="height:1px;"/>
|
||||
- `SITE_URL`: The absolute URL of your self-hosted instance of Infisical including the protocol (e.g. https://app.infisical.com)
|
||||
</Note>
|
||||
|
Before Width: | Height: | Size: 797 KiB After Width: | Height: | Size: 797 KiB |
|
Before Width: | Height: | Size: 797 KiB |
BIN
docs/images/sso/pingone-oidc/infisical-configure-oidc.png
Normal file
|
After Width: | Height: | Size: 176 KiB |
BIN
docs/images/sso/pingone-oidc/pingone-create-application.png
Normal file
|
After Width: | Height: | Size: 191 KiB |
|
After Width: | Height: | Size: 184 KiB |
|
After Width: | Height: | Size: 218 KiB |
|
After Width: | Height: | Size: 216 KiB |
|
After Width: | Height: | Size: 195 KiB |
BIN
docs/images/sso/pingone-oidc/pingone-enable-application.png
Normal file
|
After Width: | Height: | Size: 222 KiB |
BIN
docs/images/sso/pingone-oidc/pingone-overview-credentials.png
Normal file
|
After Width: | Height: | Size: 236 KiB |
|
After Width: | Height: | Size: 282 KiB |
BIN
frontend/public/images/integrations/IBM.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
@@ -1,9 +1,8 @@
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "@tanstack/react-router";
|
||||
import axios from "axios";
|
||||
import { z } from "zod";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { useSendVerificationEmail } from "@app/hooks/api";
|
||||
|
||||
import { Button, Input } from "../v2";
|
||||
@@ -35,11 +34,10 @@ export default function EnterEmailStep({
|
||||
* Verifies if the entered email "looks" correct
|
||||
*/
|
||||
const emailCheck = async () => {
|
||||
const isValid = z.string().email().safeParse(email);
|
||||
|
||||
let emailCheckBool = false;
|
||||
if (!email) {
|
||||
setEmailError(true);
|
||||
emailCheckBool = true;
|
||||
} else if (!email.includes("@") || !email.includes(".") || !/[a-z]/.test(email)) {
|
||||
if (!isValid.success) {
|
||||
setEmailError(true);
|
||||
emailCheckBool = true;
|
||||
} else {
|
||||
@@ -48,19 +46,9 @@ export default function EnterEmailStep({
|
||||
|
||||
// If everything is correct, go to the next step
|
||||
if (!emailCheckBool) {
|
||||
try {
|
||||
await mutateAsync({ email: email.toLowerCase() });
|
||||
setEmail(email.toLowerCase());
|
||||
incrementStep();
|
||||
} catch (e) {
|
||||
if (axios.isAxiosError(e)) {
|
||||
const { message = "Something went wrong" } = e.response?.data as { message: string };
|
||||
createNotification({
|
||||
type: "error",
|
||||
text: message
|
||||
});
|
||||
}
|
||||
}
|
||||
await mutateAsync({ email: email.toLowerCase() });
|
||||
setEmail(email.toLowerCase());
|
||||
incrementStep();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -12,7 +12,8 @@ export const AUDIT_LOG_STREAM_PROVIDER_MAP: Record<
|
||||
[LogProvider.Cribl]: { name: "Cribl", image: "Cribl.png", size: 60 },
|
||||
[LogProvider.Custom]: { name: "Custom", icon: faCode },
|
||||
[LogProvider.Datadog]: { name: "Datadog", image: "Datadog.png" },
|
||||
[LogProvider.Splunk]: { name: "Splunk", image: "Splunk.png", size: 65 }
|
||||
[LogProvider.Splunk]: { name: "Splunk", image: "Splunk.png", size: 65 },
|
||||
[LogProvider.QRadar]: { name: "IBM QRadar", image: "IBM.png" }
|
||||
};
|
||||
|
||||
// Strictly for showing to the client in the front-end
|
||||
|
||||
@@ -3,5 +3,6 @@ export enum LogProvider {
|
||||
Cribl = "cribl",
|
||||
Custom = "custom",
|
||||
Datadog = "datadog",
|
||||
Splunk = "splunk"
|
||||
Splunk = "splunk",
|
||||
QRadar = "qradar"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { TAzureProviderLogStream } from "./providers/azure-provider";
|
||||
import { TCriblProviderLogStream } from "./providers/cribl-provider";
|
||||
import { TCustomProviderLogStream } from "./providers/custom-provider";
|
||||
import { TDatadogProviderLogStream } from "./providers/datadog-provider";
|
||||
import { TQRadarProviderLogStream } from "./providers/qradar-provider";
|
||||
import { TSplunkProviderLogStream } from "./providers/splunk-provider";
|
||||
|
||||
export type TAuditLogStream =
|
||||
@@ -18,6 +19,7 @@ export type TAuditLogStreamProviderMap = {
|
||||
[LogProvider.Custom]: TCustomProviderLogStream;
|
||||
[LogProvider.Datadog]: TDatadogProviderLogStream;
|
||||
[LogProvider.Splunk]: TSplunkProviderLogStream;
|
||||
[LogProvider.QRadar]: TQRadarProviderLogStream;
|
||||
};
|
||||
|
||||
export type TCreateAuditLogStreamDTO = Pick<TAuditLogStream, "provider" | "credentials">;
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { LogProvider } from "../../enums";
|
||||
import { TRootProviderLogStream } from "./root-provider";
|
||||
|
||||
export type TQRadarProviderLogStream = TRootProviderLogStream & {
|
||||
provider: LogProvider.QRadar;
|
||||
// credentials: {};
|
||||
};
|
||||
@@ -2,9 +2,10 @@ import { useMemo } from "react";
|
||||
import { faSearch } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
|
||||
import { EmptyState, Spinner } from "@app/components/v2";
|
||||
import { AUDIT_LOG_STREAM_PROVIDER_MAP } from "@app/helpers/auditLogStreams";
|
||||
import { usePagination, useResetPageHelper } from "@app/hooks";
|
||||
import { usePagination, usePopUp, useResetPageHelper } from "@app/hooks";
|
||||
import { useGetAuditLogStreamOptions } from "@app/hooks/api";
|
||||
import { LogProvider } from "@app/hooks/api/auditLogStreams/enums";
|
||||
|
||||
@@ -15,6 +16,8 @@ type Props = {
|
||||
// TODO: When we have more than 1 page of providers, uncomment the search components
|
||||
|
||||
export const LogStreamProviderSelect = ({ onSelect }: Props) => {
|
||||
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["upgradePlan"]);
|
||||
|
||||
const { isPending, data: logStreamOptions } = useGetAuditLogStreamOptions();
|
||||
|
||||
const { search, setPage, page, perPage, offset } = usePagination("", {
|
||||
@@ -23,7 +26,11 @@ export const LogStreamProviderSelect = ({ onSelect }: Props) => {
|
||||
|
||||
const filteredOptions = useMemo(
|
||||
() =>
|
||||
(logStreamOptions || [])
|
||||
[
|
||||
...(logStreamOptions || []),
|
||||
// QRadar is a planned provider
|
||||
{ name: "IBM QRadar", provider: LogProvider.QRadar }
|
||||
]
|
||||
.filter(
|
||||
({ name, provider }) =>
|
||||
name.toLowerCase().includes(search.trim().toLowerCase()) ||
|
||||
@@ -54,13 +61,6 @@ export const LogStreamProviderSelect = ({ onSelect }: Props) => {
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
{/* <Input
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
|
||||
placeholder="Search options..."
|
||||
className="bg-mineshaft-800 placeholder:text-mineshaft-400"
|
||||
/> */}
|
||||
<div className="grid h-[29.5rem] grid-cols-4 content-start gap-2">
|
||||
{filteredOptions.slice(offset, perPage * page)?.map((option) => {
|
||||
const { image, icon, name, size = 50 } = AUDIT_LOG_STREAM_PROVIDER_MAP[option.provider];
|
||||
@@ -68,7 +68,13 @@ export const LogStreamProviderSelect = ({ onSelect }: Props) => {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onSelect(option.provider)}
|
||||
onClick={() => {
|
||||
if (option.provider === LogProvider.QRadar) {
|
||||
handlePopUpOpen("upgradePlan");
|
||||
} else {
|
||||
onSelect(option.provider);
|
||||
}
|
||||
}}
|
||||
className={`group relative flex h-28 cursor-pointer flex-col items-center justify-center rounded-md border border-mineshaft-600 ${option.provider === LogProvider.Custom ? "bg-mineshaft-700/30 hover:bg-mineshaft-600/30" : "bg-mineshaft-700 hover:bg-mineshaft-600"} p-4 duration-200`}
|
||||
>
|
||||
{image && (
|
||||
@@ -100,56 +106,11 @@ export const LogStreamProviderSelect = ({ onSelect }: Props) => {
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{/* {Boolean(filteredOptions.length) && (
|
||||
<Pagination
|
||||
startAdornment={
|
||||
<Tooltip
|
||||
side="bottom"
|
||||
className="max-w-sm py-4"
|
||||
content={
|
||||
<>
|
||||
<p className="mb-2">Infisical is constantly adding support for more providers.</p>
|
||||
<p>
|
||||
{`If you don't see the third-party
|
||||
provider you're looking for,`}{" "}
|
||||
<a
|
||||
target="_blank"
|
||||
className="underline hover:text-mineshaft-300"
|
||||
href="https://infisical.com/slack"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
let us know on Slack
|
||||
</a>{" "}
|
||||
or{" "}
|
||||
<a
|
||||
target="_blank"
|
||||
className="underline hover:text-mineshaft-300"
|
||||
href="https://github.com/Infisical/infisical/discussions"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
make a request on GitHub
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div className="-ml-3 flex items-center gap-1.5 text-mineshaft-400">
|
||||
<span className="text-xs">
|
||||
Don't see the third-party provider you're looking for?
|
||||
</span>
|
||||
<FontAwesomeIcon size="xs" icon={faInfoCircle} />
|
||||
</div>
|
||||
</Tooltip>
|
||||
}
|
||||
count={filteredOptions.length}
|
||||
page={page}
|
||||
perPage={perPage}
|
||||
onChangePage={setPage}
|
||||
onChangePerPage={setPerPage}
|
||||
perPageList={[16]}
|
||||
/>
|
||||
)} */}
|
||||
<UpgradePlanModal
|
||||
isOpen={popUp.upgradePlan.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||
text="This audit log stream provider requires an enterprise license."
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||