mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-16 12:37:20 +00:00
Fix Release 0.11.1 (#150)
* fix strategy forms handling, add register route and hidden fields Refactored strategy forms to include hidden fields for type and name. Added a registration route with necessary adjustments to the admin controller and routes. Corrected field handling within relevant forms and components. * fix admin access permissions and refactor routing structure display a fixed error for unmet permissions when retrieving the schema. moved auth routes outside of BkndProvider and reorganized remaining routes to include BkndWrapper. * fix: properly type BkndWrapper * bump fix release version * ModuleManager: update diff checking and AppData validation Revised diff handling includes validation of diffs, reverting changes on failure, and enforcing module constraints with onBeforeUpdate hooks. Introduced `validateDiffs` and backup of stable configs. Applied changes in related modules, tests, and UI layer to align with updated diff logic. * fix: cli: running from config file were using invalid args * fix: cli: improve sequence of onBuilt trigger to allow custom routes from cli * fix e2e tests
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { MantineProvider } from "@mantine/core";
|
||||
import { Notifications } from "@mantine/notifications";
|
||||
import React from "react";
|
||||
import React, { type ReactNode } from "react";
|
||||
import { BkndProvider, type BkndAdminOptions } from "ui/client/bknd";
|
||||
import { useTheme } from "ui/client/use-theme";
|
||||
import { Logo } from "ui/components/display/Logo";
|
||||
@@ -21,33 +21,32 @@ export default function Admin({
|
||||
withProvider = false,
|
||||
config,
|
||||
}: BkndAdminProps) {
|
||||
const Component = (
|
||||
const { theme } = useTheme();
|
||||
const Provider = ({ children }: any) =>
|
||||
withProvider ? (
|
||||
<ClientProvider
|
||||
baseUrl={baseUrlOverride}
|
||||
{...(typeof withProvider === "object" ? withProvider : {})}
|
||||
>
|
||||
{children}
|
||||
</ClientProvider>
|
||||
) : (
|
||||
children
|
||||
);
|
||||
|
||||
const BkndWrapper = ({ children }: { children: ReactNode }) => (
|
||||
<BkndProvider options={config} fallback={<Skeleton theme={config?.theme} />}>
|
||||
<AdminInternal />
|
||||
{children}
|
||||
</BkndProvider>
|
||||
);
|
||||
return withProvider ? (
|
||||
<ClientProvider
|
||||
baseUrl={baseUrlOverride}
|
||||
{...(typeof withProvider === "object" ? withProvider : {})}
|
||||
>
|
||||
{Component}
|
||||
</ClientProvider>
|
||||
) : (
|
||||
Component
|
||||
);
|
||||
}
|
||||
|
||||
function AdminInternal() {
|
||||
const { theme } = useTheme();
|
||||
|
||||
return (
|
||||
<MantineProvider {...createMantineTheme(theme as any)}>
|
||||
<Notifications position="top-right" />
|
||||
<BkndModalsProvider>
|
||||
<Routes />
|
||||
</BkndModalsProvider>
|
||||
</MantineProvider>
|
||||
<Provider>
|
||||
<MantineProvider {...createMantineTheme(theme as any)}>
|
||||
<Notifications position="top-right" />
|
||||
<Routes BkndWrapper={BkndWrapper} basePath={config?.basepath} />
|
||||
</MantineProvider>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ import { useApi } from "ui/client";
|
||||
import { type TSchemaActions, getSchemaActions } from "./schema/actions";
|
||||
import { AppReduced } from "./utils/AppReduced";
|
||||
import type { AppTheme } from "ui/client/use-theme";
|
||||
import { Message } from "ui/components/display/Message";
|
||||
import { useNavigate } from "ui/lib/routes";
|
||||
|
||||
export type BkndAdminOptions = {
|
||||
logo_return_path?: string;
|
||||
@@ -101,7 +103,6 @@ export function BkndProvider({
|
||||
fallback: true,
|
||||
} as any);
|
||||
|
||||
|
||||
startTransition(() => {
|
||||
const commit = () => {
|
||||
setSchema(newSchema);
|
||||
@@ -109,7 +110,7 @@ export function BkndProvider({
|
||||
setFetched(true);
|
||||
set_local_version((v) => v + 1);
|
||||
fetching.current = Fetching.None;
|
||||
}
|
||||
};
|
||||
|
||||
if ("startViewTransition" in document) {
|
||||
document.startViewTransition(commit);
|
||||
@@ -139,22 +140,24 @@ export function BkndProvider({
|
||||
value={{ ...schema, actions, requireSecrets, app, options: app.options, hasSecrets }}
|
||||
key={local_version}
|
||||
>
|
||||
{/*{error && (
|
||||
<Alert.Exception className="gap-2">
|
||||
<IconAlertHexagon />
|
||||
You attempted to load system configuration with secrets without having proper
|
||||
permission.
|
||||
<a href={schema.config.server.admin.basepath || "/"}>
|
||||
<Button variant="red">Reload</Button>
|
||||
</a>
|
||||
</Alert.Exception>
|
||||
)}*/}
|
||||
|
||||
{children}
|
||||
{error ? <AccessDenied /> : children}
|
||||
</BkndContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
function AccessDenied() {
|
||||
const [navigate] = useNavigate();
|
||||
return (
|
||||
<Message.MissingPermission
|
||||
what="the Admin UI"
|
||||
primary={{
|
||||
children: "Login",
|
||||
onClick: () => navigate("/auth/login"),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function useBknd({ withSecrets }: { withSecrets?: boolean } = {}): BkndContext {
|
||||
const ctx = useContext(BkndContext);
|
||||
if (withSecrets) ctx.requireSecrets();
|
||||
|
||||
@@ -16,9 +16,11 @@ const MissingPermission = ({
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
const NotEnabled = (props: Partial<EmptyProps>) => <Empty title="Not Enabled" {...props} />;
|
||||
|
||||
export const Message = {
|
||||
NotFound,
|
||||
NotAllowed,
|
||||
NotEnabled,
|
||||
MissingPermission,
|
||||
};
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
import type { JsonSchema } from "json-schema-library";
|
||||
import type { ChangeEvent, ComponentPropsWithoutRef, ReactNode } from "react";
|
||||
import {
|
||||
type ChangeEvent,
|
||||
type ComponentPropsWithoutRef,
|
||||
type ElementType,
|
||||
type ReactNode,
|
||||
useEffect,
|
||||
useId,
|
||||
} from "react";
|
||||
import ErrorBoundary from "ui/components/display/ErrorBoundary";
|
||||
import * as Formy from "ui/components/form/Formy";
|
||||
import { useEvent } from "ui/hooks/use-event";
|
||||
@@ -7,7 +14,7 @@ import { ArrayField } from "./ArrayField";
|
||||
import { FieldWrapper, type FieldwrapperProps } from "./FieldWrapper";
|
||||
import { useDerivedFieldContext, useFormValue } from "./Form";
|
||||
import { ObjectField } from "./ObjectField";
|
||||
import { coerce, isType, isTypeSchema } from "./utils";
|
||||
import { coerce, firstDefined, isType, isTypeSchema } from "./utils";
|
||||
|
||||
export type FieldProps = {
|
||||
onChange?: (e: ChangeEvent<any>) => void;
|
||||
@@ -24,6 +31,19 @@ export const Field = (props: FieldProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const HiddenField = ({
|
||||
as = "div",
|
||||
className,
|
||||
...props
|
||||
}: FieldProps & { as?: ElementType; className?: string }) => {
|
||||
const Component = as;
|
||||
return (
|
||||
<Component className={[className, "hidden"].filter(Boolean).join(" ")}>
|
||||
<Field {...props} />
|
||||
</Component>
|
||||
);
|
||||
};
|
||||
|
||||
const fieldErrorBoundary =
|
||||
({ name }: FieldProps) =>
|
||||
({ error }: { error: Error }) => (
|
||||
@@ -41,8 +61,9 @@ const FieldImpl = ({
|
||||
...props
|
||||
}: FieldProps) => {
|
||||
const { path, setValue, schema, ...ctx } = useDerivedFieldContext(name);
|
||||
const id = `${name}-${useId()}`;
|
||||
const required = typeof _required === "boolean" ? _required : ctx.required;
|
||||
//console.log("Field", { name, path, schema });
|
||||
|
||||
if (!isTypeSchema(schema))
|
||||
return (
|
||||
<Pre>
|
||||
@@ -58,7 +79,21 @@ const FieldImpl = ({
|
||||
return <ArrayField path={name} />;
|
||||
}
|
||||
|
||||
const disabled = props.disabled ?? schema.readOnly ?? "const" in schema ?? false;
|
||||
// account for `defaultValue`
|
||||
// like <Field name="name" inputProps={{ defaultValue: "oauth" }} />
|
||||
useEffect(() => {
|
||||
if (inputProps?.defaultValue) {
|
||||
setValue(path, inputProps.defaultValue);
|
||||
}
|
||||
}, [inputProps?.defaultValue]);
|
||||
|
||||
const disabled = firstDefined(
|
||||
inputProps?.disabled,
|
||||
props.disabled,
|
||||
schema.readOnly,
|
||||
"const" in schema,
|
||||
false,
|
||||
);
|
||||
|
||||
const handleChange = useEvent((e: ChangeEvent<HTMLInputElement>) => {
|
||||
const value = coerce(e.target.value, schema as any, { required });
|
||||
@@ -70,9 +105,10 @@ const FieldImpl = ({
|
||||
});
|
||||
|
||||
return (
|
||||
<FieldWrapper name={name} required={required} schema={schema} {...props}>
|
||||
<FieldWrapper name={name} required={required} schema={schema} fieldId={id} {...props}>
|
||||
<FieldComponent
|
||||
{...inputProps}
|
||||
id={id}
|
||||
schema={schema}
|
||||
name={name}
|
||||
required={required}
|
||||
@@ -93,6 +129,7 @@ export const Pre = ({ children }) => (
|
||||
export type FieldComponentProps = {
|
||||
schema: JsonSchema;
|
||||
render?: (props: Omit<FieldComponentProps, "render">) => ReactNode;
|
||||
"data-testId"?: string;
|
||||
} & ComponentPropsWithoutRef<"input">;
|
||||
|
||||
export const FieldComponent = ({ schema, render, ..._props }: FieldComponentProps) => {
|
||||
@@ -111,7 +148,7 @@ export const FieldComponent = ({ schema, render, ..._props }: FieldComponentProp
|
||||
if (render) return render({ schema, ...props });
|
||||
|
||||
if (schema.enum) {
|
||||
return <Formy.Select id={props.name} options={schema.enum} {...(props as any)} />;
|
||||
return <Formy.Select options={schema.enum} {...(props as any)} />;
|
||||
}
|
||||
|
||||
if (isType(schema.type, ["number", "integer"])) {
|
||||
@@ -121,26 +158,17 @@ export const FieldComponent = ({ schema, render, ..._props }: FieldComponentProp
|
||||
step: schema.multipleOf,
|
||||
};
|
||||
|
||||
return (
|
||||
<Formy.Input
|
||||
type="number"
|
||||
id={props.name}
|
||||
{...props}
|
||||
value={props.value ?? ""}
|
||||
{...additional}
|
||||
/>
|
||||
);
|
||||
return <Formy.Input type="number" {...props} value={props.value ?? ""} {...additional} />;
|
||||
}
|
||||
|
||||
if (isType(schema.type, "boolean")) {
|
||||
return <Formy.Switch id={props.name} {...(props as any)} checked={value === true} />;
|
||||
return <Formy.Switch {...(props as any)} checked={value === true} />;
|
||||
}
|
||||
|
||||
if (isType(schema.type, "string") && schema.format === "date-time") {
|
||||
const value = props.value ? new Date(props.value as string).toISOString().slice(0, 16) : "";
|
||||
return (
|
||||
<Formy.DateInput
|
||||
id={props.name}
|
||||
{...props}
|
||||
value={value}
|
||||
type="datetime-local"
|
||||
@@ -156,7 +184,7 @@ export const FieldComponent = ({ schema, render, ..._props }: FieldComponentProp
|
||||
}
|
||||
|
||||
if (isType(schema.type, "string") && schema.format === "date") {
|
||||
return <Formy.DateInput id={props.name} {...props} value={props.value ?? ""} />;
|
||||
return <Formy.DateInput {...props} value={props.value ?? ""} />;
|
||||
}
|
||||
|
||||
const additional = {
|
||||
@@ -171,7 +199,5 @@ export const FieldComponent = ({ schema, render, ..._props }: FieldComponentProp
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Formy.TypeAwareInput id={props.name} {...props} value={props.value ?? ""} {...additional} />
|
||||
);
|
||||
return <Formy.TypeAwareInput {...props} value={props.value ?? ""} {...additional} />;
|
||||
};
|
||||
|
||||
@@ -24,6 +24,7 @@ export type FieldwrapperProps = {
|
||||
errorPlacement?: "top" | "bottom";
|
||||
description?: string;
|
||||
descriptionPlacement?: "top" | "bottom";
|
||||
fieldId?: string;
|
||||
};
|
||||
|
||||
export function FieldWrapper({
|
||||
@@ -36,6 +37,7 @@ export function FieldWrapper({
|
||||
errorPlacement = "bottom",
|
||||
descriptionPlacement = "bottom",
|
||||
children,
|
||||
fieldId,
|
||||
...props
|
||||
}: FieldwrapperProps) {
|
||||
const errors = useFormError(name, { strict: true });
|
||||
@@ -66,7 +68,7 @@ export function FieldWrapper({
|
||||
{label && (
|
||||
<Formy.Label
|
||||
as={wrapper === "fieldset" ? "legend" : "label"}
|
||||
htmlFor={name}
|
||||
htmlFor={fieldId}
|
||||
className="self-start"
|
||||
>
|
||||
{label} {required && <span className="font-medium opacity-30">*</span>}
|
||||
|
||||
@@ -138,3 +138,10 @@ export function omitSchema<Given extends JSONSchema>(_schema: Given, keys: strin
|
||||
export function isTypeSchema(schema?: JsonSchema): schema is JsonSchema {
|
||||
return typeof schema === "object" && "type" in schema && !isType(schema.type, "error");
|
||||
}
|
||||
|
||||
export function firstDefined<T>(...args: T[]): T | undefined {
|
||||
for (const arg of args) {
|
||||
if (typeof arg !== "undefined") return arg;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ReactNode } from "react";
|
||||
import { isValidElement, type ReactNode } from "react";
|
||||
import { useAuthStrategies } from "../hooks/use-auth";
|
||||
import { AuthForm } from "./AuthForm";
|
||||
|
||||
@@ -30,11 +30,13 @@ export function AuthScreen({
|
||||
{!loading && (
|
||||
<div className="flex flex-col gap-4 items-center w-96 px-6 py-7">
|
||||
{logo ? logo : null}
|
||||
{typeof intro !== "undefined" ? (
|
||||
{isValidElement(intro) ? (
|
||||
intro
|
||||
) : (
|
||||
<div className="flex flex-col items-center">
|
||||
<h1 className="text-xl font-bold">Sign in to your admin panel</h1>
|
||||
<h1 className="text-xl font-bold">
|
||||
Sign {action === "login" ? "in" : "up"} to your admin panel
|
||||
</h1>
|
||||
<p className="text-primary/50">Enter your credentials below to get access.</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -134,7 +134,7 @@ export function Header({ hasSidebar = true }) {
|
||||
<SidebarToggler />
|
||||
<UserMenu />
|
||||
</div>
|
||||
<div className="hidden lg:flex flex-row items-center px-4 gap-2">
|
||||
<div className="hidden md:flex flex-row items-center px-4 gap-2">
|
||||
<UserMenu />
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -4,5 +4,7 @@ export const testIds = {
|
||||
data: {
|
||||
btnCreateEntity: "data-btns-create-entity",
|
||||
},
|
||||
media: {},
|
||||
media: {
|
||||
switchEnabled: "media-switch-enabled",
|
||||
},
|
||||
};
|
||||
|
||||
18
app/src/ui/routes/auth/auth.register.tsx
Normal file
18
app/src/ui/routes/auth/auth.register.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Logo } from "ui/components/display/Logo";
|
||||
import { Link } from "ui/components/wouter/Link";
|
||||
import { Auth } from "ui/elements";
|
||||
import { useBrowserTitle } from "ui/hooks/use-browser-title";
|
||||
|
||||
export function AuthRegister() {
|
||||
useBrowserTitle(["Register"]);
|
||||
return (
|
||||
<Auth.Screen
|
||||
action="register"
|
||||
logo={
|
||||
<Link href={"/"} className="link">
|
||||
<Logo scale={0.25} />
|
||||
</Link>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
Form,
|
||||
FormContextOverride,
|
||||
FormDebug,
|
||||
HiddenField,
|
||||
ObjectField,
|
||||
Subscribe,
|
||||
useDerivedFieldContext,
|
||||
@@ -36,9 +37,16 @@ import * as AppShell from "../../layouts/AppShell/AppShell";
|
||||
export function AuthStrategiesList(props) {
|
||||
useBrowserTitle(["Auth", "Strategies"]);
|
||||
|
||||
const { hasSecrets } = useBknd({ withSecrets: true });
|
||||
const {
|
||||
hasSecrets,
|
||||
config: {
|
||||
auth: { enabled },
|
||||
},
|
||||
} = useBknd({ withSecrets: true });
|
||||
if (!hasSecrets) {
|
||||
return <Message.MissingPermission what="Auth Strategies" />;
|
||||
} else if (!enabled) {
|
||||
return <Message.NotEnabled description="Enable Auth first." />;
|
||||
}
|
||||
|
||||
return <AuthStrategiesListInternal {...props} />;
|
||||
@@ -62,7 +70,6 @@ function AuthStrategiesListInternal() {
|
||||
);
|
||||
|
||||
async function handleSubmit(data: any) {
|
||||
console.log("submit", { strategies: data });
|
||||
await $auth.actions.config.set({ strategies: data });
|
||||
}
|
||||
|
||||
@@ -152,7 +159,7 @@ const Strategy = ({ type, name, unavailable }: StrategyProps) => {
|
||||
<span className="leading-none">{autoFormatString(name)}</span>
|
||||
</div>
|
||||
<div className="flex flex-row gap-4 items-center">
|
||||
<StrategyToggle />
|
||||
<StrategyToggle type={type} />
|
||||
<IconButton
|
||||
Icon={TbSettings}
|
||||
size="lg"
|
||||
@@ -168,7 +175,7 @@ const Strategy = ({ type, name, unavailable }: StrategyProps) => {
|
||||
"flex flex-col border-t border-t-muted px-4 pt-3 pb-4 bg-lightest/50 gap-4",
|
||||
)}
|
||||
>
|
||||
<StrategyForm type={type} />
|
||||
<StrategyForm type={type} name={name} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -176,7 +183,7 @@ const Strategy = ({ type, name, unavailable }: StrategyProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
const StrategyToggle = () => {
|
||||
const StrategyToggle = ({ type }: { type: StrategyProps["type"] }) => {
|
||||
const ctx = useDerivedFieldContext("");
|
||||
const { value } = useFormValue("");
|
||||
|
||||
@@ -219,8 +226,10 @@ const OAUTH_BRANDS = {
|
||||
discord: TbBrandDiscordFilled,
|
||||
};
|
||||
|
||||
const StrategyForm = ({ type }: Pick<StrategyProps, "type">) => {
|
||||
let Component = () => <ObjectField path="" wrapperProps={{ wrapper: "group", label: false }} />;
|
||||
const StrategyForm = ({ type, name }: Pick<StrategyProps, "type" | "name">) => {
|
||||
let Component = (p: any) => (
|
||||
<ObjectField path="" wrapperProps={{ wrapper: "group", label: false }} />
|
||||
);
|
||||
switch (type) {
|
||||
case "password":
|
||||
Component = StrategyPasswordForm;
|
||||
@@ -230,16 +239,22 @@ const StrategyForm = ({ type }: Pick<StrategyProps, "type">) => {
|
||||
break;
|
||||
}
|
||||
|
||||
return <Component />;
|
||||
return (
|
||||
<>
|
||||
<HiddenField name="type" inputProps={{ disabled: true, defaultValue: type }} />
|
||||
<Component type={type} name={name} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const StrategyPasswordForm = () => {
|
||||
return <ObjectField path="config" wrapperProps={{ wrapper: "group", label: false }} />;
|
||||
};
|
||||
|
||||
const StrategyOAuthForm = () => {
|
||||
const StrategyOAuthForm = ({ type, name }: Pick<StrategyProps, "type" | "name">) => {
|
||||
return (
|
||||
<>
|
||||
<HiddenField name="config.name" inputProps={{ disabled: true, defaultValue: name }} />
|
||||
<Field name="config.client.client_id" required inputProps={{ type: "password" }} />
|
||||
<Field name="config.client.client_secret" required inputProps={{ type: "password" }} />
|
||||
</>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React, { Suspense, lazy } from "react";
|
||||
import { useBknd } from "ui/client/bknd";
|
||||
import { Suspense, lazy, type ComponentType, type ReactNode } from "react";
|
||||
import { useTheme } from "ui/client/use-theme";
|
||||
import { Route, Router, Switch } from "wouter";
|
||||
import AuthRoutes from "./auth";
|
||||
@@ -10,60 +9,70 @@ import MediaRoutes from "./media";
|
||||
import { Root, RootEmpty } from "./root";
|
||||
import SettingsRoutes from "./settings";
|
||||
import { FlashMessage } from "ui/modules/server/FlashMessage";
|
||||
import { AuthRegister } from "ui/routes/auth/auth.register";
|
||||
import { BkndModalsProvider } from "ui/modals";
|
||||
|
||||
// @ts-ignore
|
||||
const TestRoutes = lazy(() => import("./test"));
|
||||
|
||||
export function Routes() {
|
||||
const { app } = useBknd();
|
||||
export function Routes({
|
||||
BkndWrapper,
|
||||
basePath = "",
|
||||
}: { BkndWrapper: ComponentType<{ children: ReactNode }>; basePath?: string }) {
|
||||
const { theme } = useTheme();
|
||||
|
||||
return (
|
||||
<div id="bknd-admin" className={theme + " antialiased"}>
|
||||
<FlashMessage />
|
||||
<Router base={app.options.basepath}>
|
||||
<Router base={basePath}>
|
||||
<Switch>
|
||||
<Route path="/auth/login" component={AuthLogin} />
|
||||
<Route path="/" nest>
|
||||
<Root>
|
||||
<Switch>
|
||||
<Route path="/test*" nest>
|
||||
<Suspense fallback={null}>
|
||||
<TestRoutes />
|
||||
</Suspense>
|
||||
</Route>
|
||||
<Route path="/auth/register" component={AuthRegister} />
|
||||
|
||||
<Route path="/" component={RootEmpty} />
|
||||
<Route path="/data" nest>
|
||||
<Suspense fallback={null}>
|
||||
<DataRoutes />
|
||||
</Suspense>
|
||||
</Route>
|
||||
<Route path="/flows" nest>
|
||||
<Suspense fallback={null}>
|
||||
<FlowRoutes />
|
||||
</Suspense>
|
||||
</Route>
|
||||
<Route path="/auth" nest>
|
||||
<Suspense fallback={null}>
|
||||
<AuthRoutes />
|
||||
</Suspense>
|
||||
</Route>
|
||||
<Route path="/media" nest>
|
||||
<Suspense fallback={null}>
|
||||
<MediaRoutes />
|
||||
</Suspense>
|
||||
</Route>
|
||||
<Route path="/settings" nest>
|
||||
<Suspense fallback={null}>
|
||||
<SettingsRoutes />
|
||||
</Suspense>
|
||||
</Route>
|
||||
<BkndWrapper>
|
||||
<BkndModalsProvider>
|
||||
<Route path="/" nest>
|
||||
<Root>
|
||||
<Switch>
|
||||
<Route path="/test*" nest>
|
||||
<Suspense fallback={null}>
|
||||
<TestRoutes />
|
||||
</Suspense>
|
||||
</Route>
|
||||
|
||||
<Route path="*" component={NotFound} />
|
||||
</Switch>
|
||||
</Root>
|
||||
</Route>
|
||||
<Route path="/" component={RootEmpty} />
|
||||
<Route path="/data" nest>
|
||||
<Suspense fallback={null}>
|
||||
<DataRoutes />
|
||||
</Suspense>
|
||||
</Route>
|
||||
<Route path="/flows" nest>
|
||||
<Suspense fallback={null}>
|
||||
<FlowRoutes />
|
||||
</Suspense>
|
||||
</Route>
|
||||
<Route path="/auth" nest>
|
||||
<Suspense fallback={null}>
|
||||
<AuthRoutes />
|
||||
</Suspense>
|
||||
</Route>
|
||||
<Route path="/media" nest>
|
||||
<Suspense fallback={null}>
|
||||
<MediaRoutes />
|
||||
</Suspense>
|
||||
</Route>
|
||||
<Route path="/settings" nest>
|
||||
<Suspense fallback={null}>
|
||||
<SettingsRoutes />
|
||||
</Suspense>
|
||||
</Route>
|
||||
|
||||
<Route path="*" component={NotFound} />
|
||||
</Switch>
|
||||
</Root>
|
||||
</Route>
|
||||
</BkndModalsProvider>
|
||||
</BkndWrapper>
|
||||
</Switch>
|
||||
</Router>
|
||||
</div>
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
} from "ui/components/form/json-schema-form";
|
||||
import { useBrowserTitle } from "ui/hooks/use-browser-title";
|
||||
import * as AppShell from "ui/layouts/AppShell/AppShell";
|
||||
import { testIds } from "ui/lib/config";
|
||||
|
||||
export function MediaSettings(props) {
|
||||
useBrowserTitle(["Media", "Settings"]);
|
||||
@@ -79,7 +80,10 @@ function MediaSettingsInternal() {
|
||||
<AppShell.Scrollable>
|
||||
<RootFormError />
|
||||
<div className="flex flex-col gap-3 p-3">
|
||||
<Field name="enabled" />
|
||||
<Field
|
||||
name="enabled"
|
||||
inputProps={{ "data-testId": testIds.media.switchEnabled }}
|
||||
/>
|
||||
<div className="flex flex-col gap-3 relative">
|
||||
<Overlay />
|
||||
<Field name="storage.body_max_size" label="Storage Body Max Size" />
|
||||
|
||||
Reference in New Issue
Block a user