mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Revert "Revert "feat(super-admin): Environment Overrides""
This commit is contained in:
21
backend/src/db/migrations/20250627010508_env-overrides.ts
Normal file
21
backend/src/db/migrations/20250627010508_env-overrides.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const hasColumn = await knex.schema.hasColumn(TableName.SuperAdmin, "encryptedEnvOverrides");
|
||||||
|
if (!hasColumn) {
|
||||||
|
await knex.schema.alterTable(TableName.SuperAdmin, (t) => {
|
||||||
|
t.binary("encryptedEnvOverrides").nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const hasColumn = await knex.schema.hasColumn(TableName.SuperAdmin, "encryptedEnvOverrides");
|
||||||
|
if (hasColumn) {
|
||||||
|
await knex.schema.alterTable(TableName.SuperAdmin, (t) => {
|
||||||
|
t.dropColumn("encryptedEnvOverrides");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,7 +34,8 @@ export const SuperAdminSchema = z.object({
|
|||||||
encryptedGitHubAppConnectionClientSecret: zodBuffer.nullable().optional(),
|
encryptedGitHubAppConnectionClientSecret: zodBuffer.nullable().optional(),
|
||||||
encryptedGitHubAppConnectionSlug: zodBuffer.nullable().optional(),
|
encryptedGitHubAppConnectionSlug: zodBuffer.nullable().optional(),
|
||||||
encryptedGitHubAppConnectionId: zodBuffer.nullable().optional(),
|
encryptedGitHubAppConnectionId: zodBuffer.nullable().optional(),
|
||||||
encryptedGitHubAppConnectionPrivateKey: zodBuffer.nullable().optional()
|
encryptedGitHubAppConnectionPrivateKey: zodBuffer.nullable().optional(),
|
||||||
|
encryptedEnvOverrides: zodBuffer.nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TSuperAdmin = z.infer<typeof SuperAdminSchema>;
|
export type TSuperAdmin = z.infer<typeof SuperAdminSchema>;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { QueueWorkerProfile } from "@app/lib/types";
|
import { QueueWorkerProfile } from "@app/lib/types";
|
||||||
|
|
||||||
|
import { BadRequestError } from "../errors";
|
||||||
import { removeTrailingSlash } from "../fn";
|
import { removeTrailingSlash } from "../fn";
|
||||||
import { CustomLogger } from "../logger/logger";
|
import { CustomLogger } from "../logger/logger";
|
||||||
import { zpStr } from "../zod";
|
import { zpStr } from "../zod";
|
||||||
@@ -341,8 +342,11 @@ const envSchema = z
|
|||||||
|
|
||||||
export type TEnvConfig = Readonly<z.infer<typeof envSchema>>;
|
export type TEnvConfig = Readonly<z.infer<typeof envSchema>>;
|
||||||
let envCfg: TEnvConfig;
|
let envCfg: TEnvConfig;
|
||||||
|
let originalEnvConfig: TEnvConfig;
|
||||||
|
|
||||||
export const getConfig = () => envCfg;
|
export const getConfig = () => envCfg;
|
||||||
|
export const getOriginalConfig = () => originalEnvConfig;
|
||||||
|
|
||||||
// cannot import singleton logger directly as it needs config to load various transport
|
// cannot import singleton logger directly as it needs config to load various transport
|
||||||
export const initEnvConfig = (logger?: CustomLogger) => {
|
export const initEnvConfig = (logger?: CustomLogger) => {
|
||||||
const parsedEnv = envSchema.safeParse(process.env);
|
const parsedEnv = envSchema.safeParse(process.env);
|
||||||
@@ -352,10 +356,115 @@ export const initEnvConfig = (logger?: CustomLogger) => {
|
|||||||
process.exit(-1);
|
process.exit(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
envCfg = Object.freeze(parsedEnv.data);
|
const config = Object.freeze(parsedEnv.data);
|
||||||
|
envCfg = config;
|
||||||
|
|
||||||
|
if (!originalEnvConfig) {
|
||||||
|
originalEnvConfig = config;
|
||||||
|
}
|
||||||
|
|
||||||
return envCfg;
|
return envCfg;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// A list of environment variables that can be overwritten
|
||||||
|
export const overwriteSchema: {
|
||||||
|
[key: string]: {
|
||||||
|
name: string;
|
||||||
|
fields: { key: keyof TEnvConfig; description?: string }[];
|
||||||
|
};
|
||||||
|
} = {
|
||||||
|
azure: {
|
||||||
|
name: "Azure",
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
key: "INF_APP_CONNECTION_AZURE_CLIENT_ID",
|
||||||
|
description: "The Application (Client) ID of your Azure application."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "INF_APP_CONNECTION_AZURE_CLIENT_SECRET",
|
||||||
|
description: "The Client Secret of your Azure application."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
google_sso: {
|
||||||
|
name: "Google SSO",
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
key: "CLIENT_ID_GOOGLE_LOGIN",
|
||||||
|
description: "The Client ID of your GCP OAuth2 application."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "CLIENT_SECRET_GOOGLE_LOGIN",
|
||||||
|
description: "The Client Secret of your GCP OAuth2 application."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
github_sso: {
|
||||||
|
name: "GitHub SSO",
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
key: "CLIENT_ID_GITHUB_LOGIN",
|
||||||
|
description: "The Client ID of your GitHub OAuth application."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "CLIENT_SECRET_GITHUB_LOGIN",
|
||||||
|
description: "The Client Secret of your GitHub OAuth application."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
gitlab_sso: {
|
||||||
|
name: "GitLab SSO",
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
key: "CLIENT_ID_GITLAB_LOGIN",
|
||||||
|
description: "The Client ID of your GitLab application."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "CLIENT_SECRET_GITLAB_LOGIN",
|
||||||
|
description: "The Secret of your GitLab application."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "CLIENT_GITLAB_LOGIN_URL",
|
||||||
|
description:
|
||||||
|
"The URL of your self-hosted instance of GitLab where the OAuth application is registered. If no URL is passed in, this will default to https://gitlab.com."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const overridableKeys = new Set(
|
||||||
|
Object.values(overwriteSchema).flatMap(({ fields }) => fields.map(({ key }) => key))
|
||||||
|
);
|
||||||
|
|
||||||
|
export const validateOverrides = (config: Record<string, string>) => {
|
||||||
|
const allowedOverrides = Object.fromEntries(
|
||||||
|
Object.entries(config).filter(([key]) => overridableKeys.has(key as keyof z.input<typeof envSchema>))
|
||||||
|
);
|
||||||
|
|
||||||
|
const tempEnv: Record<string, unknown> = { ...process.env, ...allowedOverrides };
|
||||||
|
const parsedResult = envSchema.safeParse(tempEnv);
|
||||||
|
|
||||||
|
if (!parsedResult.success) {
|
||||||
|
const errorDetails = parsedResult.error.issues
|
||||||
|
.map((issue) => `Key: "${issue.path.join(".")}", Error: ${issue.message}`)
|
||||||
|
.join("\n");
|
||||||
|
throw new BadRequestError({ message: errorDetails });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const overrideEnvConfig = (config: Record<string, string>) => {
|
||||||
|
const allowedOverrides = Object.fromEntries(
|
||||||
|
Object.entries(config).filter(([key]) => overridableKeys.has(key as keyof z.input<typeof envSchema>))
|
||||||
|
);
|
||||||
|
|
||||||
|
const tempEnv: Record<string, unknown> = { ...process.env, ...allowedOverrides };
|
||||||
|
const parsedResult = envSchema.safeParse(tempEnv);
|
||||||
|
|
||||||
|
if (parsedResult.success) {
|
||||||
|
envCfg = Object.freeze(parsedResult.data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const formatSmtpConfig = () => {
|
export const formatSmtpConfig = () => {
|
||||||
const tlsOptions: {
|
const tlsOptions: {
|
||||||
rejectUnauthorized: boolean;
|
rejectUnauthorized: boolean;
|
||||||
|
|||||||
@@ -2045,6 +2045,10 @@ export const registerRoutes = async (
|
|||||||
cronJobs.push(adminIntegrationsSyncJob);
|
cronJobs.push(adminIntegrationsSyncJob);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const configSyncJob = await superAdminService.initializeEnvConfigSync();
|
||||||
|
if (configSyncJob) {
|
||||||
|
cronJobs.push(configSyncJob);
|
||||||
|
}
|
||||||
|
|
||||||
server.decorate<FastifyZodProvider["store"]>("store", {
|
server.decorate<FastifyZodProvider["store"]>("store", {
|
||||||
user: userDAL,
|
user: userDAL,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
SuperAdminSchema,
|
SuperAdminSchema,
|
||||||
UsersSchema
|
UsersSchema
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig, overridableKeys } from "@app/lib/config/env";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
import { invalidateCacheLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { invalidateCacheLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
||||||
@@ -42,7 +42,8 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
|
|||||||
encryptedGitHubAppConnectionClientSecret: true,
|
encryptedGitHubAppConnectionClientSecret: true,
|
||||||
encryptedGitHubAppConnectionSlug: true,
|
encryptedGitHubAppConnectionSlug: true,
|
||||||
encryptedGitHubAppConnectionId: true,
|
encryptedGitHubAppConnectionId: true,
|
||||||
encryptedGitHubAppConnectionPrivateKey: true
|
encryptedGitHubAppConnectionPrivateKey: true,
|
||||||
|
encryptedEnvOverrides: true
|
||||||
}).extend({
|
}).extend({
|
||||||
isMigrationModeOn: z.boolean(),
|
isMigrationModeOn: z.boolean(),
|
||||||
defaultAuthOrgSlug: z.string().nullable(),
|
defaultAuthOrgSlug: z.string().nullable(),
|
||||||
@@ -110,11 +111,14 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
|
|||||||
.refine((content) => DOMPurify.sanitize(content) === content, {
|
.refine((content) => DOMPurify.sanitize(content) === content, {
|
||||||
message: "Page frame content contains unsafe HTML."
|
message: "Page frame content contains unsafe HTML."
|
||||||
})
|
})
|
||||||
.optional()
|
.optional(),
|
||||||
|
envOverrides: z.record(z.enum(Array.from(overridableKeys) as [string, ...string[]]), z.string()).optional()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
config: SuperAdminSchema.extend({
|
config: SuperAdminSchema.omit({
|
||||||
|
encryptedEnvOverrides: true
|
||||||
|
}).extend({
|
||||||
defaultAuthOrgSlug: z.string().nullable()
|
defaultAuthOrgSlug: z.string().nullable()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -381,6 +385,41 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/env-overrides",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
response: {
|
||||||
|
200: z.record(
|
||||||
|
z.string(),
|
||||||
|
z.object({
|
||||||
|
name: z.string(),
|
||||||
|
fields: z
|
||||||
|
.object({
|
||||||
|
key: z.string(),
|
||||||
|
value: z.string(),
|
||||||
|
hasEnvEntry: z.boolean(),
|
||||||
|
description: z.string().optional()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: (req, res, done) => {
|
||||||
|
verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN])(req, res, () => {
|
||||||
|
verifySuperAdmin(req, res, done);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handler: async () => {
|
||||||
|
const envOverrides = await server.services.superAdmin.getEnvOverridesOrganized();
|
||||||
|
return envOverrides;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
url: "/user-management/users/:userId",
|
url: "/user-management/users/:userId",
|
||||||
|
|||||||
@@ -5,7 +5,13 @@ import jwt from "jsonwebtoken";
|
|||||||
import { IdentityAuthMethod, OrgMembershipRole, TSuperAdmin, TSuperAdminUpdate } from "@app/db/schemas";
|
import { IdentityAuthMethod, OrgMembershipRole, TSuperAdmin, TSuperAdminUpdate } from "@app/db/schemas";
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { PgSqlLock, TKeyStoreFactory } from "@app/keystore/keystore";
|
import { PgSqlLock, TKeyStoreFactory } from "@app/keystore/keystore";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import {
|
||||||
|
getConfig,
|
||||||
|
getOriginalConfig,
|
||||||
|
overrideEnvConfig,
|
||||||
|
overwriteSchema,
|
||||||
|
validateOverrides
|
||||||
|
} from "@app/lib/config/env";
|
||||||
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
||||||
import { generateUserSrpKeys, getUserPrivateKey } from "@app/lib/crypto/srp";
|
import { generateUserSrpKeys, getUserPrivateKey } from "@app/lib/crypto/srp";
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
@@ -33,6 +39,7 @@ import { TInvalidateCacheQueueFactory } from "./invalidate-cache-queue";
|
|||||||
import { TSuperAdminDALFactory } from "./super-admin-dal";
|
import { TSuperAdminDALFactory } from "./super-admin-dal";
|
||||||
import {
|
import {
|
||||||
CacheType,
|
CacheType,
|
||||||
|
EnvOverrides,
|
||||||
LoginMethod,
|
LoginMethod,
|
||||||
TAdminBootstrapInstanceDTO,
|
TAdminBootstrapInstanceDTO,
|
||||||
TAdminGetIdentitiesDTO,
|
TAdminGetIdentitiesDTO,
|
||||||
@@ -234,6 +241,45 @@ export const superAdminServiceFactory = ({
|
|||||||
adminIntegrationsConfig = config;
|
adminIntegrationsConfig = config;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getEnvOverrides = async () => {
|
||||||
|
const serverCfg = await serverCfgDAL.findById(ADMIN_CONFIG_DB_UUID);
|
||||||
|
|
||||||
|
if (!serverCfg || !serverCfg.encryptedEnvOverrides) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const decrypt = kmsService.decryptWithRootKey();
|
||||||
|
|
||||||
|
const overrides = JSON.parse(decrypt(serverCfg.encryptedEnvOverrides).toString()) as Record<string, string>;
|
||||||
|
|
||||||
|
return overrides;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getEnvOverridesOrganized = async (): Promise<EnvOverrides> => {
|
||||||
|
const overrides = await getEnvOverrides();
|
||||||
|
const ogConfig = getOriginalConfig();
|
||||||
|
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(overwriteSchema).map(([groupKey, groupDef]) => [
|
||||||
|
groupKey,
|
||||||
|
{
|
||||||
|
name: groupDef.name,
|
||||||
|
fields: groupDef.fields.map(({ key, description }) => ({
|
||||||
|
key,
|
||||||
|
description,
|
||||||
|
value: overrides[key] || "",
|
||||||
|
hasEnvEntry: !!(ogConfig as unknown as Record<string, string | undefined>)[key]
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
])
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const $syncEnvConfig = async () => {
|
||||||
|
const config = await getEnvOverrides();
|
||||||
|
overrideEnvConfig(config);
|
||||||
|
};
|
||||||
|
|
||||||
const updateServerCfg = async (
|
const updateServerCfg = async (
|
||||||
data: TSuperAdminUpdate & {
|
data: TSuperAdminUpdate & {
|
||||||
slackClientId?: string;
|
slackClientId?: string;
|
||||||
@@ -246,6 +292,7 @@ export const superAdminServiceFactory = ({
|
|||||||
gitHubAppConnectionSlug?: string;
|
gitHubAppConnectionSlug?: string;
|
||||||
gitHubAppConnectionId?: string;
|
gitHubAppConnectionId?: string;
|
||||||
gitHubAppConnectionPrivateKey?: string;
|
gitHubAppConnectionPrivateKey?: string;
|
||||||
|
envOverrides?: Record<string, string>;
|
||||||
},
|
},
|
||||||
userId: string
|
userId: string
|
||||||
) => {
|
) => {
|
||||||
@@ -374,6 +421,17 @@ export const superAdminServiceFactory = ({
|
|||||||
gitHubAppConnectionSettingsUpdated = true;
|
gitHubAppConnectionSettingsUpdated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let envOverridesUpdated = false;
|
||||||
|
if (data.envOverrides !== undefined) {
|
||||||
|
// Verify input format
|
||||||
|
validateOverrides(data.envOverrides);
|
||||||
|
|
||||||
|
const encryptedEnvOverrides = encryptWithRoot(Buffer.from(JSON.stringify(data.envOverrides)));
|
||||||
|
updatedData.encryptedEnvOverrides = encryptedEnvOverrides;
|
||||||
|
updatedData.envOverrides = undefined;
|
||||||
|
envOverridesUpdated = true;
|
||||||
|
}
|
||||||
|
|
||||||
const updatedServerCfg = await serverCfgDAL.updateById(ADMIN_CONFIG_DB_UUID, updatedData);
|
const updatedServerCfg = await serverCfgDAL.updateById(ADMIN_CONFIG_DB_UUID, updatedData);
|
||||||
|
|
||||||
await keyStore.setItemWithExpiry(ADMIN_CONFIG_KEY, ADMIN_CONFIG_KEY_EXP, JSON.stringify(updatedServerCfg));
|
await keyStore.setItemWithExpiry(ADMIN_CONFIG_KEY, ADMIN_CONFIG_KEY_EXP, JSON.stringify(updatedServerCfg));
|
||||||
@@ -382,6 +440,10 @@ export const superAdminServiceFactory = ({
|
|||||||
await $syncAdminIntegrationConfig();
|
await $syncAdminIntegrationConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (envOverridesUpdated) {
|
||||||
|
await $syncEnvConfig();
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
updatedServerCfg.encryptedMicrosoftTeamsAppId &&
|
updatedServerCfg.encryptedMicrosoftTeamsAppId &&
|
||||||
updatedServerCfg.encryptedMicrosoftTeamsClientSecret &&
|
updatedServerCfg.encryptedMicrosoftTeamsClientSecret &&
|
||||||
@@ -814,6 +876,18 @@ export const superAdminServiceFactory = ({
|
|||||||
return job;
|
return job;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const initializeEnvConfigSync = async () => {
|
||||||
|
logger.info("Setting up background sync process for environment overrides");
|
||||||
|
|
||||||
|
await $syncEnvConfig();
|
||||||
|
|
||||||
|
// sync every 5 minutes
|
||||||
|
const job = new CronJob("*/5 * * * *", $syncEnvConfig);
|
||||||
|
job.start();
|
||||||
|
|
||||||
|
return job;
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
initServerCfg,
|
initServerCfg,
|
||||||
updateServerCfg,
|
updateServerCfg,
|
||||||
@@ -833,6 +907,9 @@ export const superAdminServiceFactory = ({
|
|||||||
getOrganizations,
|
getOrganizations,
|
||||||
deleteOrganization,
|
deleteOrganization,
|
||||||
deleteOrganizationMembership,
|
deleteOrganizationMembership,
|
||||||
initializeAdminIntegrationConfigSync
|
initializeAdminIntegrationConfigSync,
|
||||||
|
initializeEnvConfigSync,
|
||||||
|
getEnvOverrides,
|
||||||
|
getEnvOverridesOrganized
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { TEnvConfig } from "@app/lib/config/env";
|
||||||
|
|
||||||
export type TAdminSignUpDTO = {
|
export type TAdminSignUpDTO = {
|
||||||
email: string;
|
email: string;
|
||||||
password: string;
|
password: string;
|
||||||
@@ -74,3 +76,10 @@ export type TAdminIntegrationConfig = {
|
|||||||
privateKey: string;
|
privateKey: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface EnvOverrides {
|
||||||
|
[key: string]: {
|
||||||
|
name: string;
|
||||||
|
fields: { key: keyof TEnvConfig; value: string; hasEnvEntry: boolean; description?: string }[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
42
frontend/src/components/v2/HighlightText/HighlightText.tsx
Normal file
42
frontend/src/components/v2/HighlightText/HighlightText.tsx
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
export const HighlightText = ({
|
||||||
|
text,
|
||||||
|
highlight,
|
||||||
|
highlightClassName
|
||||||
|
}: {
|
||||||
|
text: string | undefined | null;
|
||||||
|
highlight: string;
|
||||||
|
highlightClassName?: string;
|
||||||
|
}) => {
|
||||||
|
if (!text) return null;
|
||||||
|
const searchTerm = highlight.toLowerCase().trim();
|
||||||
|
|
||||||
|
if (!searchTerm) return <span>{text}</span>;
|
||||||
|
|
||||||
|
const parts: React.ReactNode[] = [];
|
||||||
|
let lastIndex = 0;
|
||||||
|
|
||||||
|
const escapedSearchTerm = searchTerm.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||||
|
const regex = new RegExp(escapedSearchTerm, "gi");
|
||||||
|
|
||||||
|
text.replace(regex, (match: string, offset: number) => {
|
||||||
|
if (offset > lastIndex) {
|
||||||
|
parts.push(<span key={`pre-${lastIndex}`}>{text.substring(lastIndex, offset)}</span>);
|
||||||
|
}
|
||||||
|
|
||||||
|
parts.push(
|
||||||
|
<span key={`match-${offset}`} className={highlightClassName || "bg-yellow/30"}>
|
||||||
|
{match}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
|
||||||
|
lastIndex = offset + match.length;
|
||||||
|
|
||||||
|
return match;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (lastIndex < text.length) {
|
||||||
|
parts.push(<span key={`post-${lastIndex}`}>{text.substring(lastIndex)}</span>);
|
||||||
|
}
|
||||||
|
|
||||||
|
return parts;
|
||||||
|
};
|
||||||
1
frontend/src/components/v2/HighlightText/index.tsx
Normal file
1
frontend/src/components/v2/HighlightText/index.tsx
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { HighlightText } from "./HighlightText";
|
||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
AdminGetUsersFilters,
|
AdminGetUsersFilters,
|
||||||
AdminIntegrationsConfig,
|
AdminIntegrationsConfig,
|
||||||
OrganizationWithProjects,
|
OrganizationWithProjects,
|
||||||
|
TGetEnvOverrides,
|
||||||
TGetInvalidatingCacheStatus,
|
TGetInvalidatingCacheStatus,
|
||||||
TGetServerRootKmsEncryptionDetails,
|
TGetServerRootKmsEncryptionDetails,
|
||||||
TServerConfig
|
TServerConfig
|
||||||
@@ -31,7 +32,8 @@ export const adminQueryKeys = {
|
|||||||
getAdminSlackConfig: () => ["admin-slack-config"] as const,
|
getAdminSlackConfig: () => ["admin-slack-config"] as const,
|
||||||
getServerEncryptionStrategies: () => ["server-encryption-strategies"] as const,
|
getServerEncryptionStrategies: () => ["server-encryption-strategies"] as const,
|
||||||
getInvalidateCache: () => ["admin-invalidate-cache"] as const,
|
getInvalidateCache: () => ["admin-invalidate-cache"] as const,
|
||||||
getAdminIntegrationsConfig: () => ["admin-integrations-config"] as const
|
getAdminIntegrationsConfig: () => ["admin-integrations-config"] as const,
|
||||||
|
getEnvOverrides: () => ["env-overrides"] as const
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchServerConfig = async () => {
|
export const fetchServerConfig = async () => {
|
||||||
@@ -163,3 +165,13 @@ export const useGetInvalidatingCacheStatus = (enabled = true) => {
|
|||||||
refetchInterval: (data) => (data ? 3000 : false)
|
refetchInterval: (data) => (data ? 3000 : false)
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useGetEnvOverrides = () => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: adminQueryKeys.getEnvOverrides(),
|
||||||
|
queryFn: async () => {
|
||||||
|
const { data } = await apiRequest.get<TGetEnvOverrides>("/api/v1/admin/env-overrides");
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ export type TServerConfig = {
|
|||||||
authConsentContent?: string;
|
authConsentContent?: string;
|
||||||
pageFrameContent?: string;
|
pageFrameContent?: string;
|
||||||
invalidatingCache: boolean;
|
invalidatingCache: boolean;
|
||||||
|
envOverrides?: Record<string, string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TUpdateServerConfigDTO = {
|
export type TUpdateServerConfigDTO = {
|
||||||
@@ -61,6 +62,7 @@ export type TUpdateServerConfigDTO = {
|
|||||||
gitHubAppConnectionSlug?: string;
|
gitHubAppConnectionSlug?: string;
|
||||||
gitHubAppConnectionId?: string;
|
gitHubAppConnectionId?: string;
|
||||||
gitHubAppConnectionPrivateKey?: string;
|
gitHubAppConnectionPrivateKey?: string;
|
||||||
|
envOverrides?: Record<string, string>;
|
||||||
} & Partial<TServerConfig>;
|
} & Partial<TServerConfig>;
|
||||||
|
|
||||||
export type TCreateAdminUserDTO = {
|
export type TCreateAdminUserDTO = {
|
||||||
@@ -138,3 +140,10 @@ export type TInvalidateCacheDTO = {
|
|||||||
export type TGetInvalidatingCacheStatus = {
|
export type TGetInvalidatingCacheStatus = {
|
||||||
invalidating: boolean;
|
invalidating: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface TGetEnvOverrides {
|
||||||
|
[key: string]: {
|
||||||
|
name: string;
|
||||||
|
fields: { key: string; value: string; hasEnvEntry: boolean; description?: string }[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -41,6 +41,11 @@ const generalTabs = [
|
|||||||
label: "Caching",
|
label: "Caching",
|
||||||
icon: "note",
|
icon: "note",
|
||||||
link: "/admin/caching"
|
link: "/admin/caching"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Environment",
|
||||||
|
icon: "unlock",
|
||||||
|
link: "/admin/environment"
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
27
frontend/src/pages/admin/EnvironmentPage/EnvironmentPage.tsx
Normal file
27
frontend/src/pages/admin/EnvironmentPage/EnvironmentPage.tsx
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { Helmet } from "react-helmet";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
import { PageHeader } from "@app/components/v2";
|
||||||
|
|
||||||
|
import { EnvironmentPageForm } from "./components";
|
||||||
|
|
||||||
|
export const EnvironmentPage = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="bg-bunker-800">
|
||||||
|
<Helmet>
|
||||||
|
<title>{t("common.head-title", { title: "Admin" })}</title>
|
||||||
|
</Helmet>
|
||||||
|
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
|
||||||
|
<div className="mx-auto mb-6 w-full max-w-7xl">
|
||||||
|
<PageHeader
|
||||||
|
title="Environment"
|
||||||
|
description="Manage the environment for your Infisical instance."
|
||||||
|
/>
|
||||||
|
<EnvironmentPageForm />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,245 @@
|
|||||||
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
|
import { Control, Controller, useForm, useWatch } from "react-hook-form";
|
||||||
|
import {
|
||||||
|
faChevronRight,
|
||||||
|
faExclamationTriangle,
|
||||||
|
faMagnifyingGlass
|
||||||
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { createNotification } from "@app/components/notifications";
|
||||||
|
import { Button, FormControl, Input, SecretInput, Tooltip } from "@app/components/v2";
|
||||||
|
import { HighlightText } from "@app/components/v2/HighlightText";
|
||||||
|
import { useGetEnvOverrides, useUpdateServerConfig } from "@app/hooks/api";
|
||||||
|
|
||||||
|
type TForm = Record<string, string>;
|
||||||
|
|
||||||
|
export const GroupContainer = ({
|
||||||
|
group,
|
||||||
|
control,
|
||||||
|
search
|
||||||
|
}: {
|
||||||
|
group: {
|
||||||
|
fields: {
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
hasEnvEntry: boolean;
|
||||||
|
description?: string;
|
||||||
|
}[];
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
control: Control<TForm, any, TForm>;
|
||||||
|
search: string;
|
||||||
|
}) => {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={group.name}
|
||||||
|
className="overflow-clip border border-b-0 border-mineshaft-600 bg-mineshaft-800 first:rounded-t-md last:rounded-b-md last:border-b"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="flex h-14 cursor-pointer items-center px-5 py-4 text-sm text-gray-300"
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
onClick={() => setOpen((o) => !o)}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
setOpen((o) => !o);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
className={`mr-8 transition-transform duration-100 ${open || search ? "rotate-90" : ""}`}
|
||||||
|
icon={faChevronRight}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="flex-grow select-none text-base">{group.name}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{(open || search) && (
|
||||||
|
<div className="flex flex-col">
|
||||||
|
{group.fields.map((field) => (
|
||||||
|
<div
|
||||||
|
key={field.key}
|
||||||
|
className="flex items-center justify-between gap-4 border-t border-mineshaft-500 bg-mineshaft-700/50 p-4"
|
||||||
|
>
|
||||||
|
<div className="flex max-w-lg flex-col">
|
||||||
|
<span className="text-sm">
|
||||||
|
<HighlightText text={field.key} highlight={search} />
|
||||||
|
</span>
|
||||||
|
<span className="text-sm text-mineshaft-400">
|
||||||
|
<HighlightText text={field.description} highlight={search} />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex grow items-center justify-end gap-2">
|
||||||
|
{field.hasEnvEntry && (
|
||||||
|
<Tooltip
|
||||||
|
content="Setting this value will override an existing environment variable"
|
||||||
|
className="text-center"
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faExclamationTriangle} className="text-yellow" />
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name={field.key}
|
||||||
|
render={({ field: formGenField, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
className="mb-0 w-full max-w-sm"
|
||||||
|
>
|
||||||
|
<SecretInput
|
||||||
|
{...formGenField}
|
||||||
|
autoComplete="off"
|
||||||
|
containerClassName="text-bunker-300 hover:border-mineshaft-400 border border-mineshaft-600 bg-bunker-600 px-2 py-1.5"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EnvironmentPageForm = () => {
|
||||||
|
const { data: envOverrides } = useGetEnvOverrides();
|
||||||
|
const { mutateAsync: updateServerConfig } = useUpdateServerConfig();
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
|
|
||||||
|
const allFields = useMemo(() => {
|
||||||
|
if (!envOverrides) return [];
|
||||||
|
return Object.values(envOverrides).flatMap((group) => group.fields);
|
||||||
|
}, [envOverrides]);
|
||||||
|
|
||||||
|
const formSchema = useMemo(() => {
|
||||||
|
return z.object(Object.fromEntries(allFields.map((field) => [field.key, z.string()])));
|
||||||
|
}, [allFields]);
|
||||||
|
|
||||||
|
const defaultValues = useMemo(() => {
|
||||||
|
const values: Record<string, string> = {};
|
||||||
|
allFields.forEach((field) => {
|
||||||
|
values[field.key] = field.value ?? "";
|
||||||
|
});
|
||||||
|
return values;
|
||||||
|
}, [allFields]);
|
||||||
|
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
reset,
|
||||||
|
formState: { isSubmitting, isDirty }
|
||||||
|
} = useForm<TForm>({
|
||||||
|
resolver: zodResolver(formSchema),
|
||||||
|
defaultValues
|
||||||
|
});
|
||||||
|
|
||||||
|
const formValues = useWatch({ control });
|
||||||
|
|
||||||
|
const filteredData = useMemo(() => {
|
||||||
|
if (!envOverrides) return [];
|
||||||
|
|
||||||
|
const searchTerm = search.toLowerCase().trim();
|
||||||
|
if (!searchTerm) {
|
||||||
|
return Object.values(envOverrides);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.values(envOverrides)
|
||||||
|
.map((group) => {
|
||||||
|
const filteredFields = group.fields.filter(
|
||||||
|
(field) =>
|
||||||
|
field.key.toLowerCase().includes(searchTerm) ||
|
||||||
|
(field.description ?? "").toLowerCase().includes(searchTerm)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (filteredFields.length > 0) {
|
||||||
|
return { ...group, fields: filteredFields };
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
}, [search, formValues, envOverrides]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
reset(defaultValues);
|
||||||
|
}, [defaultValues, reset]);
|
||||||
|
|
||||||
|
const onSubmit = useCallback(
|
||||||
|
async (formData: TForm) => {
|
||||||
|
try {
|
||||||
|
const filteredFormData = Object.fromEntries(
|
||||||
|
Object.entries(formData).filter(([, value]) => value !== "")
|
||||||
|
);
|
||||||
|
await updateServerConfig({
|
||||||
|
envOverrides: filteredFormData
|
||||||
|
});
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
type: "success",
|
||||||
|
text: "Environment overrides updated successfully. It can take up to 5 minutes to take effect."
|
||||||
|
});
|
||||||
|
|
||||||
|
reset(formData);
|
||||||
|
} catch (error) {
|
||||||
|
const errorMessage =
|
||||||
|
(error as any)?.response?.data?.message ||
|
||||||
|
(error as any)?.message ||
|
||||||
|
"An unknown error occurred";
|
||||||
|
createNotification({
|
||||||
|
type: "error",
|
||||||
|
title: "Failed to update environment overrides",
|
||||||
|
text: errorMessage
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[reset, updateServerConfig]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form
|
||||||
|
className="flex flex-col gap-4 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"
|
||||||
|
onSubmit={handleSubmit(onSubmit)}
|
||||||
|
>
|
||||||
|
<div className="flex w-full flex-row items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<div className="flex items-start gap-1">
|
||||||
|
<p className="text-xl font-semibold text-mineshaft-100">Overrides</p>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-bunker-300">Override specific environment variables.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-row gap-2">
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
variant="outline_bg"
|
||||||
|
isLoading={isSubmitting}
|
||||||
|
isDisabled={isSubmitting || !isDirty}
|
||||||
|
>
|
||||||
|
Save Overrides
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Input
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
|
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
|
||||||
|
placeholder="Search for keys, descriptions, and values..."
|
||||||
|
className="flex-1"
|
||||||
|
/>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
{filteredData.map((group) => (
|
||||||
|
<GroupContainer group={group!} control={control} search={search} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { EnvironmentPageForm } from "./EnvironmentPageForm";
|
||||||
25
frontend/src/pages/admin/EnvironmentPage/route.tsx
Normal file
25
frontend/src/pages/admin/EnvironmentPage/route.tsx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { createFileRoute, linkOptions } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
import { EnvironmentPage } from "./EnvironmentPage";
|
||||||
|
|
||||||
|
export const Route = createFileRoute(
|
||||||
|
"/_authenticate/_inject-org-details/admin/_admin-layout/environment"
|
||||||
|
)({
|
||||||
|
component: EnvironmentPage,
|
||||||
|
beforeLoad: async () => {
|
||||||
|
return {
|
||||||
|
breadcrumbs: [
|
||||||
|
{
|
||||||
|
label: "Admin",
|
||||||
|
link: linkOptions({ to: "/admin" })
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Environment",
|
||||||
|
link: linkOptions({
|
||||||
|
to: "/admin/environment"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -43,6 +43,7 @@ import { Route as authProviderSuccessPageRouteImport } from './pages/auth/Provid
|
|||||||
import { Route as authProviderErrorPageRouteImport } from './pages/auth/ProviderErrorPage/route'
|
import { Route as authProviderErrorPageRouteImport } from './pages/auth/ProviderErrorPage/route'
|
||||||
import { Route as userPersonalSettingsPageRouteImport } from './pages/user/PersonalSettingsPage/route'
|
import { Route as userPersonalSettingsPageRouteImport } from './pages/user/PersonalSettingsPage/route'
|
||||||
import { Route as adminIntegrationsPageRouteImport } from './pages/admin/IntegrationsPage/route'
|
import { Route as adminIntegrationsPageRouteImport } from './pages/admin/IntegrationsPage/route'
|
||||||
|
import { Route as adminEnvironmentPageRouteImport } from './pages/admin/EnvironmentPage/route'
|
||||||
import { Route as adminEncryptionPageRouteImport } from './pages/admin/EncryptionPage/route'
|
import { Route as adminEncryptionPageRouteImport } from './pages/admin/EncryptionPage/route'
|
||||||
import { Route as adminCachingPageRouteImport } from './pages/admin/CachingPage/route'
|
import { Route as adminCachingPageRouteImport } from './pages/admin/CachingPage/route'
|
||||||
import { Route as adminAuthenticationPageRouteImport } from './pages/admin/AuthenticationPage/route'
|
import { Route as adminAuthenticationPageRouteImport } from './pages/admin/AuthenticationPage/route'
|
||||||
@@ -607,6 +608,12 @@ const adminIntegrationsPageRouteRoute = adminIntegrationsPageRouteImport.update(
|
|||||||
} as any,
|
} as any,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const adminEnvironmentPageRouteRoute = adminEnvironmentPageRouteImport.update({
|
||||||
|
id: '/environment',
|
||||||
|
path: '/environment',
|
||||||
|
getParentRoute: () => adminLayoutRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
const adminEncryptionPageRouteRoute = adminEncryptionPageRouteImport.update({
|
const adminEncryptionPageRouteRoute = adminEncryptionPageRouteImport.update({
|
||||||
id: '/encryption',
|
id: '/encryption',
|
||||||
path: '/encryption',
|
path: '/encryption',
|
||||||
@@ -2353,6 +2360,13 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof adminEncryptionPageRouteImport
|
preLoaderRoute: typeof adminEncryptionPageRouteImport
|
||||||
parentRoute: typeof adminLayoutImport
|
parentRoute: typeof adminLayoutImport
|
||||||
}
|
}
|
||||||
|
'/_authenticate/_inject-org-details/admin/_admin-layout/environment': {
|
||||||
|
id: '/_authenticate/_inject-org-details/admin/_admin-layout/environment'
|
||||||
|
path: '/environment'
|
||||||
|
fullPath: '/admin/environment'
|
||||||
|
preLoaderRoute: typeof adminEnvironmentPageRouteImport
|
||||||
|
parentRoute: typeof adminLayoutImport
|
||||||
|
}
|
||||||
'/_authenticate/_inject-org-details/admin/_admin-layout/integrations': {
|
'/_authenticate/_inject-org-details/admin/_admin-layout/integrations': {
|
||||||
id: '/_authenticate/_inject-org-details/admin/_admin-layout/integrations'
|
id: '/_authenticate/_inject-org-details/admin/_admin-layout/integrations'
|
||||||
path: '/integrations'
|
path: '/integrations'
|
||||||
@@ -4484,6 +4498,7 @@ interface adminLayoutRouteChildren {
|
|||||||
adminAuthenticationPageRouteRoute: typeof adminAuthenticationPageRouteRoute
|
adminAuthenticationPageRouteRoute: typeof adminAuthenticationPageRouteRoute
|
||||||
adminCachingPageRouteRoute: typeof adminCachingPageRouteRoute
|
adminCachingPageRouteRoute: typeof adminCachingPageRouteRoute
|
||||||
adminEncryptionPageRouteRoute: typeof adminEncryptionPageRouteRoute
|
adminEncryptionPageRouteRoute: typeof adminEncryptionPageRouteRoute
|
||||||
|
adminEnvironmentPageRouteRoute: typeof adminEnvironmentPageRouteRoute
|
||||||
adminIntegrationsPageRouteRoute: typeof adminIntegrationsPageRouteRoute
|
adminIntegrationsPageRouteRoute: typeof adminIntegrationsPageRouteRoute
|
||||||
adminMachineIdentitiesResourcesPageRouteRoute: typeof adminMachineIdentitiesResourcesPageRouteRoute
|
adminMachineIdentitiesResourcesPageRouteRoute: typeof adminMachineIdentitiesResourcesPageRouteRoute
|
||||||
adminOrganizationResourcesPageRouteRoute: typeof adminOrganizationResourcesPageRouteRoute
|
adminOrganizationResourcesPageRouteRoute: typeof adminOrganizationResourcesPageRouteRoute
|
||||||
@@ -4495,6 +4510,7 @@ const adminLayoutRouteChildren: adminLayoutRouteChildren = {
|
|||||||
adminAuthenticationPageRouteRoute: adminAuthenticationPageRouteRoute,
|
adminAuthenticationPageRouteRoute: adminAuthenticationPageRouteRoute,
|
||||||
adminCachingPageRouteRoute: adminCachingPageRouteRoute,
|
adminCachingPageRouteRoute: adminCachingPageRouteRoute,
|
||||||
adminEncryptionPageRouteRoute: adminEncryptionPageRouteRoute,
|
adminEncryptionPageRouteRoute: adminEncryptionPageRouteRoute,
|
||||||
|
adminEnvironmentPageRouteRoute: adminEnvironmentPageRouteRoute,
|
||||||
adminIntegrationsPageRouteRoute: adminIntegrationsPageRouteRoute,
|
adminIntegrationsPageRouteRoute: adminIntegrationsPageRouteRoute,
|
||||||
adminMachineIdentitiesResourcesPageRouteRoute:
|
adminMachineIdentitiesResourcesPageRouteRoute:
|
||||||
adminMachineIdentitiesResourcesPageRouteRoute,
|
adminMachineIdentitiesResourcesPageRouteRoute,
|
||||||
@@ -4697,6 +4713,7 @@ export interface FileRoutesByFullPath {
|
|||||||
'/admin/authentication': typeof adminAuthenticationPageRouteRoute
|
'/admin/authentication': typeof adminAuthenticationPageRouteRoute
|
||||||
'/admin/caching': typeof adminCachingPageRouteRoute
|
'/admin/caching': typeof adminCachingPageRouteRoute
|
||||||
'/admin/encryption': typeof adminEncryptionPageRouteRoute
|
'/admin/encryption': typeof adminEncryptionPageRouteRoute
|
||||||
|
'/admin/environment': typeof adminEnvironmentPageRouteRoute
|
||||||
'/admin/integrations': typeof adminIntegrationsPageRouteRoute
|
'/admin/integrations': typeof adminIntegrationsPageRouteRoute
|
||||||
'/cert-manager/$projectId': typeof certManagerLayoutRouteWithChildren
|
'/cert-manager/$projectId': typeof certManagerLayoutRouteWithChildren
|
||||||
'/kms/$projectId': typeof kmsLayoutRouteWithChildren
|
'/kms/$projectId': typeof kmsLayoutRouteWithChildren
|
||||||
@@ -4918,6 +4935,7 @@ export interface FileRoutesByTo {
|
|||||||
'/admin/authentication': typeof adminAuthenticationPageRouteRoute
|
'/admin/authentication': typeof adminAuthenticationPageRouteRoute
|
||||||
'/admin/caching': typeof adminCachingPageRouteRoute
|
'/admin/caching': typeof adminCachingPageRouteRoute
|
||||||
'/admin/encryption': typeof adminEncryptionPageRouteRoute
|
'/admin/encryption': typeof adminEncryptionPageRouteRoute
|
||||||
|
'/admin/environment': typeof adminEnvironmentPageRouteRoute
|
||||||
'/admin/integrations': typeof adminIntegrationsPageRouteRoute
|
'/admin/integrations': typeof adminIntegrationsPageRouteRoute
|
||||||
'/cert-manager/$projectId': typeof certManagerLayoutRouteWithChildren
|
'/cert-manager/$projectId': typeof certManagerLayoutRouteWithChildren
|
||||||
'/kms/$projectId': typeof kmsLayoutRouteWithChildren
|
'/kms/$projectId': typeof kmsLayoutRouteWithChildren
|
||||||
@@ -5139,6 +5157,7 @@ export interface FileRoutesById {
|
|||||||
'/_authenticate/_inject-org-details/admin/_admin-layout/authentication': typeof adminAuthenticationPageRouteRoute
|
'/_authenticate/_inject-org-details/admin/_admin-layout/authentication': typeof adminAuthenticationPageRouteRoute
|
||||||
'/_authenticate/_inject-org-details/admin/_admin-layout/caching': typeof adminCachingPageRouteRoute
|
'/_authenticate/_inject-org-details/admin/_admin-layout/caching': typeof adminCachingPageRouteRoute
|
||||||
'/_authenticate/_inject-org-details/admin/_admin-layout/encryption': typeof adminEncryptionPageRouteRoute
|
'/_authenticate/_inject-org-details/admin/_admin-layout/encryption': typeof adminEncryptionPageRouteRoute
|
||||||
|
'/_authenticate/_inject-org-details/admin/_admin-layout/environment': typeof adminEnvironmentPageRouteRoute
|
||||||
'/_authenticate/_inject-org-details/admin/_admin-layout/integrations': typeof adminIntegrationsPageRouteRoute
|
'/_authenticate/_inject-org-details/admin/_admin-layout/integrations': typeof adminIntegrationsPageRouteRoute
|
||||||
'/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId': typeof AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdRouteWithChildren
|
'/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId': typeof AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdRouteWithChildren
|
||||||
'/_authenticate/_inject-org-details/_org-layout/kms/$projectId': typeof AuthenticateInjectOrgDetailsOrgLayoutKmsProjectIdRouteWithChildren
|
'/_authenticate/_inject-org-details/_org-layout/kms/$projectId': typeof AuthenticateInjectOrgDetailsOrgLayoutKmsProjectIdRouteWithChildren
|
||||||
@@ -5371,6 +5390,7 @@ export interface FileRouteTypes {
|
|||||||
| '/admin/authentication'
|
| '/admin/authentication'
|
||||||
| '/admin/caching'
|
| '/admin/caching'
|
||||||
| '/admin/encryption'
|
| '/admin/encryption'
|
||||||
|
| '/admin/environment'
|
||||||
| '/admin/integrations'
|
| '/admin/integrations'
|
||||||
| '/cert-manager/$projectId'
|
| '/cert-manager/$projectId'
|
||||||
| '/kms/$projectId'
|
| '/kms/$projectId'
|
||||||
@@ -5591,6 +5611,7 @@ export interface FileRouteTypes {
|
|||||||
| '/admin/authentication'
|
| '/admin/authentication'
|
||||||
| '/admin/caching'
|
| '/admin/caching'
|
||||||
| '/admin/encryption'
|
| '/admin/encryption'
|
||||||
|
| '/admin/environment'
|
||||||
| '/admin/integrations'
|
| '/admin/integrations'
|
||||||
| '/cert-manager/$projectId'
|
| '/cert-manager/$projectId'
|
||||||
| '/kms/$projectId'
|
| '/kms/$projectId'
|
||||||
@@ -5810,6 +5831,7 @@ export interface FileRouteTypes {
|
|||||||
| '/_authenticate/_inject-org-details/admin/_admin-layout/authentication'
|
| '/_authenticate/_inject-org-details/admin/_admin-layout/authentication'
|
||||||
| '/_authenticate/_inject-org-details/admin/_admin-layout/caching'
|
| '/_authenticate/_inject-org-details/admin/_admin-layout/caching'
|
||||||
| '/_authenticate/_inject-org-details/admin/_admin-layout/encryption'
|
| '/_authenticate/_inject-org-details/admin/_admin-layout/encryption'
|
||||||
|
| '/_authenticate/_inject-org-details/admin/_admin-layout/environment'
|
||||||
| '/_authenticate/_inject-org-details/admin/_admin-layout/integrations'
|
| '/_authenticate/_inject-org-details/admin/_admin-layout/integrations'
|
||||||
| '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId'
|
| '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId'
|
||||||
| '/_authenticate/_inject-org-details/_org-layout/kms/$projectId'
|
| '/_authenticate/_inject-org-details/_org-layout/kms/$projectId'
|
||||||
@@ -6267,6 +6289,7 @@ export const routeTree = rootRoute
|
|||||||
"/_authenticate/_inject-org-details/admin/_admin-layout/authentication",
|
"/_authenticate/_inject-org-details/admin/_admin-layout/authentication",
|
||||||
"/_authenticate/_inject-org-details/admin/_admin-layout/caching",
|
"/_authenticate/_inject-org-details/admin/_admin-layout/caching",
|
||||||
"/_authenticate/_inject-org-details/admin/_admin-layout/encryption",
|
"/_authenticate/_inject-org-details/admin/_admin-layout/encryption",
|
||||||
|
"/_authenticate/_inject-org-details/admin/_admin-layout/environment",
|
||||||
"/_authenticate/_inject-org-details/admin/_admin-layout/integrations",
|
"/_authenticate/_inject-org-details/admin/_admin-layout/integrations",
|
||||||
"/_authenticate/_inject-org-details/admin/_admin-layout/resources/machine-identities",
|
"/_authenticate/_inject-org-details/admin/_admin-layout/resources/machine-identities",
|
||||||
"/_authenticate/_inject-org-details/admin/_admin-layout/resources/organizations",
|
"/_authenticate/_inject-org-details/admin/_admin-layout/resources/organizations",
|
||||||
@@ -6309,6 +6332,10 @@ export const routeTree = rootRoute
|
|||||||
"filePath": "admin/EncryptionPage/route.tsx",
|
"filePath": "admin/EncryptionPage/route.tsx",
|
||||||
"parent": "/_authenticate/_inject-org-details/admin/_admin-layout"
|
"parent": "/_authenticate/_inject-org-details/admin/_admin-layout"
|
||||||
},
|
},
|
||||||
|
"/_authenticate/_inject-org-details/admin/_admin-layout/environment": {
|
||||||
|
"filePath": "admin/EnvironmentPage/route.tsx",
|
||||||
|
"parent": "/_authenticate/_inject-org-details/admin/_admin-layout"
|
||||||
|
},
|
||||||
"/_authenticate/_inject-org-details/admin/_admin-layout/integrations": {
|
"/_authenticate/_inject-org-details/admin/_admin-layout/integrations": {
|
||||||
"filePath": "admin/IntegrationsPage/route.tsx",
|
"filePath": "admin/IntegrationsPage/route.tsx",
|
||||||
"parent": "/_authenticate/_inject-org-details/admin/_admin-layout"
|
"parent": "/_authenticate/_inject-org-details/admin/_admin-layout"
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ const adminRoute = route("/admin", [
|
|||||||
index("admin/GeneralPage/route.tsx"),
|
index("admin/GeneralPage/route.tsx"),
|
||||||
route("/encryption", "admin/EncryptionPage/route.tsx"),
|
route("/encryption", "admin/EncryptionPage/route.tsx"),
|
||||||
route("/authentication", "admin/AuthenticationPage/route.tsx"),
|
route("/authentication", "admin/AuthenticationPage/route.tsx"),
|
||||||
|
route("/environment", "admin/EnvironmentPage/route.tsx"),
|
||||||
route("/integrations", "admin/IntegrationsPage/route.tsx"),
|
route("/integrations", "admin/IntegrationsPage/route.tsx"),
|
||||||
route("/caching", "admin/CachingPage/route.tsx"),
|
route("/caching", "admin/CachingPage/route.tsx"),
|
||||||
route("/resources/organizations", "admin/OrganizationResourcesPage/route.tsx"),
|
route("/resources/organizations", "admin/OrganizationResourcesPage/route.tsx"),
|
||||||
|
|||||||
Reference in New Issue
Block a user