From 0df5c761ec704cf7f21969bc6f74f7adb2d393e2 Mon Sep 17 00:00:00 2001 From: dswbx Date: Thu, 21 Nov 2024 08:23:16 +0100 Subject: [PATCH] fix: when auth is disabled, the users entity doesn't exist --- app/src/ui/components/display/Alert.tsx | 35 +++++++++++ app/src/ui/components/display/Empty.tsx | 2 +- app/src/ui/components/display/Message.tsx | 7 +++ app/src/ui/layouts/AppShell/AppShell.tsx | 2 +- app/src/ui/routes/auth/_auth.root.tsx | 10 +++- app/src/ui/routes/auth/auth.index.tsx | 25 ++++---- app/src/ui/routes/auth/auth.roles.tsx | 36 ++--------- app/src/ui/routes/auth/auth.settings.tsx | 51 +++++++++++++--- app/src/ui/routes/data/data.$entity.$id.tsx | 11 ++-- app/src/ui/routes/data/data.$entity.index.tsx | 20 ++++--- .../ui/routes/settings/components/Setting.tsx | 11 ++-- .../settings/components/SettingNewModal.tsx | 8 ++- .../routes/settings/routes/auth.settings.tsx | 9 +++ app/vite.dev.ts | 59 ++++++++++++++++++- 14 files changed, 212 insertions(+), 74 deletions(-) create mode 100644 app/src/ui/components/display/Alert.tsx create mode 100644 app/src/ui/components/display/Message.tsx diff --git a/app/src/ui/components/display/Alert.tsx b/app/src/ui/components/display/Alert.tsx new file mode 100644 index 0000000..6f35fba --- /dev/null +++ b/app/src/ui/components/display/Alert.tsx @@ -0,0 +1,35 @@ +import type { ComponentPropsWithoutRef } from "react"; +import { twMerge } from "tailwind-merge"; + +export type AlertProps = ComponentPropsWithoutRef<"div"> & { + className?: string; + visible?: boolean; + title?: string; + message?: string; +}; + +const Base: React.FC = ({ visible = true, title, message, className, ...props }) => + visible ? ( +
+
+ {title && {title}:} + {message} +
+
+ ) : null; + +const Warning: React.FC = (props) => ( + +); + +const Exception: React.FC = (props) => ( + +); + +export const Alert = { + Warning, + Exception +}; diff --git a/app/src/ui/components/display/Empty.tsx b/app/src/ui/components/display/Empty.tsx index b6e5b55..717b781 100644 --- a/app/src/ui/components/display/Empty.tsx +++ b/app/src/ui/components/display/Empty.tsx @@ -1,6 +1,6 @@ import { Button } from "../buttons/Button"; -type EmptyProps = { +export type EmptyProps = { Icon?: any; title?: string; description?: string; diff --git a/app/src/ui/components/display/Message.tsx b/app/src/ui/components/display/Message.tsx new file mode 100644 index 0000000..34069dd --- /dev/null +++ b/app/src/ui/components/display/Message.tsx @@ -0,0 +1,7 @@ +import { Empty, type EmptyProps } from "./Empty"; + +const NotFound = (props: Partial) => ; + +export const Message = { + NotFound +}; diff --git a/app/src/ui/layouts/AppShell/AppShell.tsx b/app/src/ui/layouts/AppShell/AppShell.tsx index d6f2ce3..003aba4 100644 --- a/app/src/ui/layouts/AppShell/AppShell.tsx +++ b/app/src/ui/layouts/AppShell/AppShell.tsx @@ -194,7 +194,7 @@ export const SidebarLink = ({ "flex flex-row px-4 py-2.5 items-center gap-2", !disabled && "cursor-pointer rounded-md [&.active]:bg-primary/10 [&.active]:hover:bg-primary/15 [&.active]:font-medium hover:bg-primary/5 link", - disabled && "opacity-50 cursor-not-allowed", + disabled && "opacity-50 cursor-not-allowed pointer-events-none", className )} > diff --git a/app/src/ui/routes/auth/_auth.root.tsx b/app/src/ui/routes/auth/_auth.root.tsx index 08629a5..8930236 100644 --- a/app/src/ui/routes/auth/_auth.root.tsx +++ b/app/src/ui/routes/auth/_auth.root.tsx @@ -33,16 +33,22 @@ export function AuthRoot({ children }) { Users - + Roles & Permissions Strategies - + {/**/} + Settings diff --git a/app/src/ui/routes/auth/auth.index.tsx b/app/src/ui/routes/auth/auth.index.tsx index 2439a82..92190f3 100644 --- a/app/src/ui/routes/auth/auth.index.tsx +++ b/app/src/ui/routes/auth/auth.index.tsx @@ -1,4 +1,6 @@ import { useBknd, useClient } from "ui/client"; +import { useBkndAuth } from "ui/client/schema/auth/use-bknd-auth"; +import { Alert } from "ui/components/display/Alert"; import { routes } from "ui/lib/routes"; import { Button, @@ -10,15 +12,13 @@ import * as AppShell from "../../layouts/AppShell/AppShell"; export function AuthIndex() { const client = useClient(); - const { app, config } = useBknd(); - const users_entity = config.auth.entity_name; + const { app } = useBknd(); + const { + config: { roles, strategies, entity_name, enabled } + } = useBkndAuth(); + const users_entity = entity_name; const query = client.query().data.entity("users").count(); const usersTotal = query.data?.body.count ?? 0; - const { - config: { - auth: { roles, strategies } - } - } = useBknd(); const rolesTotal = Object.keys(roles ?? {}).length ?? 0; const strategiesTotal = Object.keys(strategies ?? {}).length ?? 0; @@ -30,11 +30,16 @@ export function AuthIndex() { <> Overview +
(null); const { config, actions } = useBkndAuth(); const data = Object.values( @@ -64,27 +56,6 @@ export function AuthRolesList() { return ( <> - {/* - -
- - -
-
*/} @@ -95,6 +66,11 @@ export function AuthRolesList() { Roles & Permissions +
; + const { app } = useBknd(); + const [navigate] = useNavigate(); + useEffect(() => { + navigate(app.getSettingsPath(["auth"])); + }, []); + + /*useBknd({ withSecrets: true }); + return ;*/ } const uiSchema = { @@ -21,9 +36,11 @@ const uiSchema = { }; function AuthSettingsListInternal() { - const s = useBknd(); - const config = s.config.auth; - const schema = cloneDeep(omit(s.schema.auth, ["title"])); + const $auth = useBkndAuth(); + const { entities } = useBkndData(); + const formRef = useRef(null); + const config = $auth.config; + const schema = cloneDeep(omit($auth.schema, ["title"])); const [generalSchema, generalConfig, extracted] = extractSchema(schema as any, config, [ "jwt", "roles", @@ -32,7 +49,6 @@ function AuthSettingsListInternal() { ]); try { const user_entity = config.entity_name ?? "users"; - const entities = s.config.data.entities ?? {}; const user_fields = Object.entries(entities[user_entity]?.fields ?? {}) .map(([name, field]) => (!field.config?.virtual ? name : undefined)) .filter(Boolean); @@ -42,18 +58,34 @@ function AuthSettingsListInternal() { extracted.jwt.schema.properties.fields.items.enum = user_fields; extracted.jwt.schema.properties.fields.uniqueItems = true; uiSchema.jwt.fields["ui:widget"] = "checkboxes"; + } else { + uiSchema.jwt.fields["ui:widget"] = "hidden"; } } catch (e) { console.error(e); } - console.log({ generalSchema, generalConfig, extracted }); + + async function handleSubmit() { + console.log(formRef.current?.validateForm(), formRef.current?.formData()); + } return ( <> - Update}> + + Update + + } + > Settings +

JWT Settings

(null); const [navigate] = useNavigate(); useBrowserTitle(["Data", entity.label, `#${entityId}`]); - const targetRelations = app.relations.listableRelationsOf(entity); - console.log("targetRelations", targetRelations, app.relations.relationsOf(entity)); + const targetRelations = relations.listableRelationsOf(entity); + //console.log("targetRelations", targetRelations, relations.relationsOf(entity)); // filter out polymorphic for now //.filter((r) => r.type() !== "poly"); - const local_relation_refs = app.relations + const local_relation_refs = relations .sourceRelationsOf(entity) ?.map((r) => r.other(entity).reference); diff --git a/app/src/ui/routes/data/data.$entity.index.tsx b/app/src/ui/routes/data/data.$entity.index.tsx index fb6f8a8..33e7add 100644 --- a/app/src/ui/routes/data/data.$entity.index.tsx +++ b/app/src/ui/routes/data/data.$entity.index.tsx @@ -1,6 +1,9 @@ import { Type } from "core/utils"; import { querySchema } from "data"; import { TbDots } from "react-icons/tb"; +import { useBkndData } from "ui/client/schema/data/use-bknd-data"; +import { Empty } from "ui/components/display/Empty"; +import { Message } from "ui/components/display/Message"; import { EntityTable2 } from "ui/modules/data/components/EntityTable2"; import { useBknd } from "../../client"; import { Button } from "../../components/buttons/Button"; @@ -25,22 +28,21 @@ const searchSchema = Type.Composite( ); export function DataEntityList({ params }) { - console.log("params", params); - const { app } = useBknd(); - const entity = app.entity(params.entity as string)!; + const { $data, relations } = useBkndData(); + const entity = $data.entity(params.entity as string); const [navigate] = useNavigate(); const search = useSearch(searchSchema, { - select: entity.getSelect(undefined, "table"), - sort: entity.getDefaultSort() + select: entity?.getSelect(undefined, "table") ?? [], + sort: entity?.getDefaultSort() }); console.log("search", search.value); - useBrowserTitle(["Data", entity.label]); + useBrowserTitle(["Data", entity?.label ?? params.entity]); const PER_PAGE_OPTIONS = [5, 10, 25]; //console.log("search", search.value); function handleClickRow(row: Record) { - navigate(routes.data.entity.edit(entity.name, row.id)); + if (entity) navigate(routes.data.entity.edit(entity.name, row.id)); } function handleClickPage(page: number) { @@ -61,6 +63,10 @@ export function DataEntityList({ params }) { search.set("perPage", perPage); } + if (!entity) { + return ; + } + return ( <> boolean; allowEdit?: (config: any) => boolean; showAlert?: (config: any) => string | ReactNode | undefined; + reloadOnSave?: boolean; }; properties?: { [key in keyof Partial]: { @@ -151,6 +153,9 @@ export function Setting({ console.log("save:success", success); if (success) { + if (options?.reloadOnSave) { + window.location.reload(); + } //window.location.reload(); } else { setSubmitting(false); @@ -229,11 +234,7 @@ export function Setting({ - {showAlert && ( -
- {showAlert} -
- )} + {typeof showAlert === "string" && }
{ + navigate(prefixPath + newKey, { + replace: true + }); + }, 500); } else { setSubmitting(false); } diff --git a/app/src/ui/routes/settings/routes/auth.settings.tsx b/app/src/ui/routes/settings/routes/auth.settings.tsx index 41e2550..d996297 100644 --- a/app/src/ui/routes/settings/routes/auth.settings.tsx +++ b/app/src/ui/routes/settings/routes/auth.settings.tsx @@ -115,6 +115,15 @@ export const AuthSettings = ({ schema: _unsafe_copy, config }) => { schema={_schema} uiSchema={uiSchema} config={config} + options={{ + showAlert: (config: any) => { + if (!config.enabled) { + return "Auth is disabled. Enable it by toggling the switch below. Please also make sure set a secure secret to sign JWT tokens."; + } + return; + }, + reloadOnSave: true + }} properties={{ strategies: { extract: true, diff --git a/app/vite.dev.ts b/app/vite.dev.ts index 32bc615..788406d 100644 --- a/app/vite.dev.ts +++ b/app/vite.dev.ts @@ -1,9 +1,66 @@ +import { readFile } from "node:fs/promises"; +import { serveStatic } from "@hono/node-server/serve-static"; import { createClient } from "@libsql/client/node"; -import { serveFresh } from "./src/adapter/vite"; +import { App, type BkndConfig } from "./src"; import { LibsqlConnection } from "./src/data"; import { StorageLocalAdapter } from "./src/media/storage/adapters/StorageLocalAdapter"; import { registries } from "./src/modules/registries"; +async function getHtml() { + return readFile("index.html", "utf8"); +} +function addViteScripts(html: string) { + return html.replace( + "", + ` + +` + ); +} + +function createApp(config: BkndConfig, env: any) { + const create_config = typeof config.app === "function" ? config.app(env) : config.app; + return App.create(create_config); +} + +function setAppBuildListener(app: App, config: BkndConfig, html: string) { + app.emgr.on( + "app-built", + async () => { + await config.onBuilt?.(app); + app.module.server.setAdminHtml(html); + app.module.server.client.get("/assets/!*", serveStatic({ root: "./" })); + }, + "sync" + ); +} + +export async function serveFresh(config: BkndConfig, _html?: string) { + let html = _html; + if (!html) { + html = await getHtml(); + } + + html = addViteScripts(html); + + return { + async fetch(request: Request, env: any) { + const app = createApp(config, env); + + setAppBuildListener(app, config, html); + await app.build(); + + return app.fetch(request, env); + } + }; +} + registries.media.add("local", { cls: StorageLocalAdapter, schema: StorageLocalAdapter.prototype.getSchema()