Review fixes

This commit is contained in:
x032205
2025-06-28 03:01:34 -04:00
parent 10939fecc0
commit a389ede03d
8 changed files with 106 additions and 100 deletions
+5 -3
View File
@@ -366,7 +366,7 @@ export const initEnvConfig = (logger?: CustomLogger) => {
export const overwriteSchema: { export const overwriteSchema: {
[key: string]: { [key: string]: {
name: string; name: string;
fields: { key: keyof z.input<typeof envSchema>; description?: string }[]; fields: { key: keyof TEnvConfig; description?: string }[];
}; };
} = { } = {
google_sso: { google_sso: {
@@ -395,7 +395,7 @@ export const overwriteSchema: {
} }
] ]
}, },
github: { gitlab_sso: {
name: "GitLab SSO", name: "GitLab SSO",
fields: [ fields: [
{ {
@@ -415,7 +415,9 @@ export const overwriteSchema: {
} }
}; };
const overridableKeys = new Set(Object.values(overwriteSchema).flatMap(({ fields }) => fields.map(({ key }) => key))); export const overridableKeys = new Set(
Object.values(overwriteSchema).flatMap(({ fields }) => fields.map(({ key }) => key))
);
export const overrideEnvConfig = (config: Record<string, string>) => { export const overrideEnvConfig = (config: Record<string, string>) => {
const allowedOverrides = Object.fromEntries( const allowedOverrides = Object.fromEntries(
+5 -3
View File
@@ -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";
@@ -112,11 +112,13 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
message: "Page frame content contains unsafe HTML." message: "Page frame content contains unsafe HTML."
}) })
.optional(), .optional(),
envOverrides: z.record(z.string(), z.string()).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()
}) })
}) })
@@ -1,10 +1,11 @@
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
import { registerSyncSecretsEndpoints } from "./secret-sync-endpoints";
import { import {
CloudflarePagesSyncSchema, CloudflarePagesSyncSchema,
CreateCloudflarePagesSyncSchema, CreateCloudflarePagesSyncSchema,
UpdateCloudflarePagesSyncSchema UpdateCloudflarePagesSyncSchema
} from "@app/services/secret-sync/cloudflare-pages/cloudflare-pages-schema"; } from "@app/services/secret-sync/cloudflare-pages/cloudflare-pages-schema";
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
import { registerSyncSecretsEndpoints } from "./secret-sync-endpoints";
export const registerCloudflarePagesSyncRouter = async (server: FastifyZodProvider) => export const registerCloudflarePagesSyncRouter = async (server: FastifyZodProvider) =>
registerSyncSecretsEndpoints({ registerSyncSecretsEndpoints({
@@ -1,6 +1,7 @@
import z from "zod"; import z from "zod";
import { AppConnections } from "@app/lib/api-docs"; import { AppConnections } from "@app/lib/api-docs";
import { CharacterType, characterValidator } from "@app/lib/validator/validate-string";
import { AppConnection } from "@app/services/app-connection/app-connection-enums"; import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { import {
BaseAppConnectionSchema, BaseAppConnectionSchema,
@@ -9,7 +10,6 @@ import {
} from "@app/services/app-connection/app-connection-schemas"; } from "@app/services/app-connection/app-connection-schemas";
import { CloudflareConnectionMethod } from "./cloudflare-connection-enum"; import { CloudflareConnectionMethod } from "./cloudflare-connection-enum";
import { CharacterType, characterValidator } from "@app/lib/validator/validate-string";
const accountIdCharacterValidator = characterValidator([ const accountIdCharacterValidator = characterValidator([
CharacterType.AlphaNumeric, CharacterType.AlphaNumeric,
@@ -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;
@@ -78,6 +80,6 @@ export type TAdminIntegrationConfig = {
export interface EnvOverrides { export interface EnvOverrides {
[key: string]: { [key: string]: {
name: string; name: string;
fields: { key: string; value: string; hasEnvEntry: boolean; description?: string }[]; fields: { key: keyof TEnvConfig; value: string; hasEnvEntry: boolean; description?: string }[];
}; };
} }
@@ -164,7 +164,10 @@ export const MinimizedOrgSidebar = () => {
const handleCopyToken = async () => { const handleCopyToken = async () => {
try { try {
await window.navigator.clipboard.writeText(getAuthToken()); await window.navigator.clipboard.writeText(getAuthToken());
createNotification({ type: "success", text: "Copied current login session token to clipboard" }); createNotification({
type: "success",
text: "Copied current login session token to clipboard"
});
} catch (error) { } catch (error) {
console.log(error); console.log(error);
createNotification({ type: "error", text: "Failed to copy user token to clipboard" }); createNotification({ type: "error", text: "Failed to copy user token to clipboard" });
@@ -2,6 +2,7 @@ import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { PageHeader } from "@app/components/v2"; import { PageHeader } from "@app/components/v2";
import { EnvironmentPageForm } from "./components"; import { EnvironmentPageForm } from "./components";
export const EnvironmentPage = () => { export const EnvironmentPage = () => {
@@ -1,12 +1,12 @@
import { useCallback, useEffect, useMemo, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
import { Controller, useForm, useWatch } from "react-hook-form"; import { Controller, useForm, useWatch } from "react-hook-form";
import { faExclamationTriangle, faMagnifyingGlass } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod"; import { z } from "zod";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
import { Button, FormControl, Input, SecretInput, Tooltip } from "@app/components/v2"; import { Button, FormControl, Input, SecretInput, Tooltip } from "@app/components/v2";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faExclamationTriangle, faMagnifyingGlass } from "@fortawesome/free-solid-svg-icons";
import { useGetEnvOverrides, useUpdateServerConfig } from "@app/hooks/api"; import { useGetEnvOverrides, useUpdateServerConfig } from "@app/hooks/api";
type TForm = Record<string, string>; type TForm = Record<string, string>;
@@ -55,14 +55,11 @@ export const EnvironmentPageForm = () => {
return Object.values(envOverrides) return Object.values(envOverrides)
.map((group) => { .map((group) => {
const filteredFields = group.fields.filter((field) => { const filteredFields = group.fields.filter(
const value = formValues?.[field.key] ?? ""; (field) =>
return (
field.key.toLowerCase().includes(searchTerm) || field.key.toLowerCase().includes(searchTerm) ||
(field.description ?? "").toLowerCase().includes(searchTerm) || (field.description ?? "").toLowerCase().includes(searchTerm)
value.toLowerCase().includes(searchTerm) );
);
});
if (filteredFields.length > 0) { if (filteredFields.length > 0) {
return { ...group, fields: filteredFields }; return { ...group, fields: filteredFields };
@@ -103,91 +100,89 @@ export const EnvironmentPageForm = () => {
); );
return ( return (
<> <form
<form className="flex flex-col gap-4 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"
className="flex flex-col gap-4 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4" onSubmit={handleSubmit(onSubmit)}
onSubmit={handleSubmit(onSubmit)} >
> <div className="flex w-full flex-row items-center justify-between">
<div className="flex w-full flex-row items-center justify-between"> <div>
<div> <div className="flex items-start gap-1">
<div className="flex items-start gap-1"> <p className="text-xl font-semibold text-mineshaft-100">Overrides</p>
<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>
<p className="text-sm text-bunker-300">Override specific environment variables.</p>
</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 gap-4">
{filteredData.map((group) => (
<div key={group!.name}>
<span className="mb-2 text-sm text-mineshaft-300">{group!.name}</span>
<div className="rounded-lg border border-mineshaft-700 bg-mineshaft-800">
<div className="flex flex-col">
{group!.fields.map((field, i) => (
<div
key={field.key}
className={`flex items-center justify-between gap-4 border-mineshaft-500 p-4 ${
i === 0 ? "" : "border-t"
}`}
>
<div className="flex max-w-lg flex-col">
<span className="text-sm">{field.key}</span>
<span className="text-sm text-mineshaft-400">{field.description}</span>
</div>
<div className="flex grow items-center justify-end gap-2"> <div className="flex flex-row gap-2">
{field.hasEnvEntry && ( <Button
<Tooltip type="submit"
content="Setting this value will override an existing environment variable" variant="outline_bg"
className="text-center" isLoading={isSubmitting}
> isDisabled={isSubmitting || !isDirty}
<FontAwesomeIcon icon={faExclamationTriangle} className="text-red" /> >
</Tooltip> Save Overrides
)} </Button>
</div>
<Controller </div>
control={control} <Input
name={field.key} value={search}
render={({ field: formGenField, fieldState: { error } }) => ( onChange={(e) => setSearch(e.target.value)}
<FormControl leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
isError={Boolean(error)} placeholder="Search for keys, descriptions, and values..."
errorText={error?.message} className="flex-1"
className="mb-0 w-full max-w-sm" />
> <div className="flex flex-col gap-4">
<SecretInput {filteredData.map((group) => (
{...formGenField} <div key={group!.name}>
autoComplete="off" <span className="mb-2 text-sm text-mineshaft-300">{group!.name}</span>
containerClassName="text-bunker-300 hover:border-mineshaft-400 border border-mineshaft-600 bg-bunker-600 px-2 py-1.5" <div className="rounded-lg border border-mineshaft-700 bg-mineshaft-800">
/> <div className="flex flex-col">
</FormControl> {group!.fields.map((field, i) => (
)} <div
/> key={field.key}
</div> className={`flex items-center justify-between gap-4 border-mineshaft-500 p-4 ${
i === 0 ? "" : "border-t"
}`}
>
<div className="flex max-w-lg flex-col">
<span className="text-sm">{field.key}</span>
<span className="text-sm text-mineshaft-400">{field.description}</span>
</div> </div>
))}
</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-red" />
</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>
</div> </div>
))} </div>
</div> ))}
</form> </div>
</> </form>
); );
}; };