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:
dswbx
2025-04-20 09:29:58 +02:00
committed by GitHub
parent 2988e4c3bd
commit 4c11789ea8
29 changed files with 520 additions and 169 deletions

View 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>
}
/>
);
}

View File

@@ -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" }} />
</>

View File

@@ -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>

View File

@@ -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" />