mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-16 04:27:21 +00:00
fix: when auth is disabled, the users entity doesn't exist
This commit is contained in:
35
app/src/ui/components/display/Alert.tsx
Normal file
35
app/src/ui/components/display/Alert.tsx
Normal file
@@ -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<AlertProps> = ({ visible = true, title, message, className, ...props }) =>
|
||||||
|
visible ? (
|
||||||
|
<div
|
||||||
|
{...props}
|
||||||
|
className={twMerge("flex flex-row dark:bg-amber-300/20 bg-amber-200 p-4", className)}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
{title && <b className="mr-2">{title}:</b>}
|
||||||
|
{message}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null;
|
||||||
|
|
||||||
|
const Warning: React.FC<AlertProps> = (props) => (
|
||||||
|
<Base {...props} className="dark:bg-amber-300/20 bg-amber-200" />
|
||||||
|
);
|
||||||
|
|
||||||
|
const Exception: React.FC<AlertProps> = (props) => (
|
||||||
|
<Base {...props} className="dark:bg-red-950 bg-red-100" />
|
||||||
|
);
|
||||||
|
|
||||||
|
export const Alert = {
|
||||||
|
Warning,
|
||||||
|
Exception
|
||||||
|
};
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Button } from "../buttons/Button";
|
import { Button } from "../buttons/Button";
|
||||||
|
|
||||||
type EmptyProps = {
|
export type EmptyProps = {
|
||||||
Icon?: any;
|
Icon?: any;
|
||||||
title?: string;
|
title?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
|
|||||||
7
app/src/ui/components/display/Message.tsx
Normal file
7
app/src/ui/components/display/Message.tsx
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { Empty, type EmptyProps } from "./Empty";
|
||||||
|
|
||||||
|
const NotFound = (props: Partial<EmptyProps>) => <Empty title="Not Found" {...props} />;
|
||||||
|
|
||||||
|
export const Message = {
|
||||||
|
NotFound
|
||||||
|
};
|
||||||
@@ -194,7 +194,7 @@ export const SidebarLink = <E extends React.ElementType = "a">({
|
|||||||
"flex flex-row px-4 py-2.5 items-center gap-2",
|
"flex flex-row px-4 py-2.5 items-center gap-2",
|
||||||
!disabled &&
|
!disabled &&
|
||||||
"cursor-pointer rounded-md [&.active]:bg-primary/10 [&.active]:hover:bg-primary/15 [&.active]:font-medium hover:bg-primary/5 link",
|
"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
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -33,16 +33,22 @@ export function AuthRoot({ children }) {
|
|||||||
<AppShell.SidebarLink
|
<AppShell.SidebarLink
|
||||||
as={Link}
|
as={Link}
|
||||||
href={app.getAbsolutePath("/data/" + routes.data.entity.list(users_entity))}
|
href={app.getAbsolutePath("/data/" + routes.data.entity.list(users_entity))}
|
||||||
|
disabled={!config.auth.enabled}
|
||||||
>
|
>
|
||||||
Users
|
Users
|
||||||
</AppShell.SidebarLink>
|
</AppShell.SidebarLink>
|
||||||
<AppShell.SidebarLink as={Link} href={routes.auth.roles.list()}>
|
<AppShell.SidebarLink
|
||||||
|
as={Link}
|
||||||
|
href={routes.auth.roles.list()}
|
||||||
|
disabled={!config.auth.enabled}
|
||||||
|
>
|
||||||
Roles & Permissions
|
Roles & Permissions
|
||||||
</AppShell.SidebarLink>
|
</AppShell.SidebarLink>
|
||||||
<AppShell.SidebarLink as={Link} href={routes.auth.strategies()} disabled>
|
<AppShell.SidebarLink as={Link} href={routes.auth.strategies()} disabled>
|
||||||
Strategies
|
Strategies
|
||||||
</AppShell.SidebarLink>
|
</AppShell.SidebarLink>
|
||||||
<AppShell.SidebarLink as={Link} href={routes.auth.settings()}>
|
{/*<AppShell.SidebarLink as={Link} href={routes.auth.settings()}>*/}
|
||||||
|
<AppShell.SidebarLink as={Link} href={app.getSettingsPath(["auth"])}>
|
||||||
Settings
|
Settings
|
||||||
</AppShell.SidebarLink>
|
</AppShell.SidebarLink>
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import { useBknd, useClient } from "ui/client";
|
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 { routes } from "ui/lib/routes";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@@ -10,15 +12,13 @@ import * as AppShell from "../../layouts/AppShell/AppShell";
|
|||||||
|
|
||||||
export function AuthIndex() {
|
export function AuthIndex() {
|
||||||
const client = useClient();
|
const client = useClient();
|
||||||
const { app, config } = useBknd();
|
const { app } = useBknd();
|
||||||
const users_entity = config.auth.entity_name;
|
const {
|
||||||
|
config: { roles, strategies, entity_name, enabled }
|
||||||
|
} = useBkndAuth();
|
||||||
|
const users_entity = entity_name;
|
||||||
const query = client.query().data.entity("users").count();
|
const query = client.query().data.entity("users").count();
|
||||||
const usersTotal = query.data?.body.count ?? 0;
|
const usersTotal = query.data?.body.count ?? 0;
|
||||||
const {
|
|
||||||
config: {
|
|
||||||
auth: { roles, strategies }
|
|
||||||
}
|
|
||||||
} = useBknd();
|
|
||||||
const rolesTotal = Object.keys(roles ?? {}).length ?? 0;
|
const rolesTotal = Object.keys(roles ?? {}).length ?? 0;
|
||||||
const strategiesTotal = Object.keys(strategies ?? {}).length ?? 0;
|
const strategiesTotal = Object.keys(strategies ?? {}).length ?? 0;
|
||||||
|
|
||||||
@@ -30,11 +30,16 @@ export function AuthIndex() {
|
|||||||
<>
|
<>
|
||||||
<AppShell.SectionHeader>Overview</AppShell.SectionHeader>
|
<AppShell.SectionHeader>Overview</AppShell.SectionHeader>
|
||||||
<AppShell.Scrollable>
|
<AppShell.Scrollable>
|
||||||
|
<Alert.Warning
|
||||||
|
visible={!enabled}
|
||||||
|
title="Auth not enabled"
|
||||||
|
message="To use authentication features, please enable it in the settings."
|
||||||
|
/>
|
||||||
<div className="flex flex-col flex-grow p-3 gap-3">
|
<div className="flex flex-col flex-grow p-3 gap-3">
|
||||||
<div className="grid xs:grid-cols-1 sm:grid-cols-2 xl:grid-cols-4 gap-5">
|
<div className="grid xs:grid-cols-1 sm:grid-cols-2 xl:grid-cols-4 gap-5">
|
||||||
<KpiCard
|
<KpiCard
|
||||||
title="Users registered"
|
title="Users registered"
|
||||||
value={usersTotal}
|
value={!enabled ? 0 : usersTotal}
|
||||||
actions={[
|
actions={[
|
||||||
{
|
{
|
||||||
label: "View all",
|
label: "View all",
|
||||||
@@ -45,7 +50,7 @@ export function AuthIndex() {
|
|||||||
/>
|
/>
|
||||||
<KpiCard
|
<KpiCard
|
||||||
title="Roles"
|
title="Roles"
|
||||||
value={rolesTotal}
|
value={!enabled ? 0 : rolesTotal}
|
||||||
actions={[
|
actions={[
|
||||||
{ label: "View all", href: rolesLink },
|
{ label: "View all", href: rolesLink },
|
||||||
{ label: "Add new", variant: "default", href: rolesLink }
|
{ label: "Add new", variant: "default", href: rolesLink }
|
||||||
@@ -53,7 +58,7 @@ export function AuthIndex() {
|
|||||||
/>
|
/>
|
||||||
<KpiCard
|
<KpiCard
|
||||||
title="Strategies enabled"
|
title="Strategies enabled"
|
||||||
value={strategiesTotal}
|
value={!enabled ? 0 : strategiesTotal}
|
||||||
actions={[
|
actions={[
|
||||||
{ label: "View all", href: strategiesLink },
|
{ label: "View all", href: strategiesLink },
|
||||||
{ label: "Add new", variant: "default", href: strategiesLink }
|
{ label: "Add new", variant: "default", href: strategiesLink }
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
import { Modal, TextInput } from "@mantine/core";
|
|
||||||
import { useDisclosure, useFocusTrap } from "@mantine/hooks";
|
|
||||||
import { StringIdentifier, transformObject, ucFirstAllSnakeToPascalWithSpaces } from "core/utils";
|
import { StringIdentifier, transformObject, ucFirstAllSnakeToPascalWithSpaces } from "core/utils";
|
||||||
import { useRef } from "react";
|
|
||||||
import { useBkndAuth } from "ui/client/schema/auth/use-bknd-auth";
|
import { useBkndAuth } from "ui/client/schema/auth/use-bknd-auth";
|
||||||
import { JsonSchemaForm } from "ui/components/form/json-schema/JsonSchemaForm";
|
import { Alert } from "ui/components/display/Alert";
|
||||||
import { bkndModals } from "ui/modals";
|
import { bkndModals } from "ui/modals";
|
||||||
import { SchemaFormModal } from "ui/modals/debug/SchemaFormModal";
|
|
||||||
import { useBknd } from "../../client/BkndProvider";
|
|
||||||
import { Button } from "../../components/buttons/Button";
|
import { Button } from "../../components/buttons/Button";
|
||||||
import { CellValue, DataTable } from "../../components/table/DataTable";
|
import { CellValue, DataTable } from "../../components/table/DataTable";
|
||||||
import * as AppShell from "../../layouts/AppShell/AppShell";
|
import * as AppShell from "../../layouts/AppShell/AppShell";
|
||||||
@@ -14,9 +9,6 @@ import { routes, useNavigate } from "../../lib/routes";
|
|||||||
|
|
||||||
export function AuthRolesList() {
|
export function AuthRolesList() {
|
||||||
const [navigate] = useNavigate();
|
const [navigate] = useNavigate();
|
||||||
const [modalOpen, modalHandler] = useDisclosure(false);
|
|
||||||
const focusRef = useFocusTrap();
|
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
|
||||||
const { config, actions } = useBkndAuth();
|
const { config, actions } = useBkndAuth();
|
||||||
|
|
||||||
const data = Object.values(
|
const data = Object.values(
|
||||||
@@ -64,27 +56,6 @@ export function AuthRolesList() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/*<Modal
|
|
||||||
ref={focusRef}
|
|
||||||
opened={modalOpen}
|
|
||||||
onClose={modalHandler.close}
|
|
||||||
title={"New Role"}
|
|
||||||
classNames={{
|
|
||||||
root: "bknd-admin",
|
|
||||||
header: "!bg-primary/5 border-b border-b-muted !py-3 px-5 !h-auto !min-h-px",
|
|
||||||
content: "rounded-lg select-none",
|
|
||||||
title: "font-bold !text-md",
|
|
||||||
body: "pt-3 pb-3 px-3 gap-4 flex flex-col"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<TextInput ref={inputRef} data-autofocus size="md" placeholder="Enter role name" />
|
|
||||||
<div className="flex flex-row justify-end gap-2">
|
|
||||||
<Button onClick={() => modalHandler.close()}>Cancel</Button>
|
|
||||||
<Button variant="primary" onClick={handleClickAdd}>
|
|
||||||
Create
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Modal>*/}
|
|
||||||
<AppShell.SectionHeader
|
<AppShell.SectionHeader
|
||||||
right={
|
right={
|
||||||
<Button variant="primary" onClick={openCreateModal}>
|
<Button variant="primary" onClick={openCreateModal}>
|
||||||
@@ -95,6 +66,11 @@ export function AuthRolesList() {
|
|||||||
Roles & Permissions
|
Roles & Permissions
|
||||||
</AppShell.SectionHeader>
|
</AppShell.SectionHeader>
|
||||||
<AppShell.Scrollable>
|
<AppShell.Scrollable>
|
||||||
|
<Alert.Warning
|
||||||
|
visible={!config.enabled}
|
||||||
|
title="Auth not enabled"
|
||||||
|
message="To use authentication features, please enable it in the settings."
|
||||||
|
/>
|
||||||
<div className="flex flex-col flex-grow p-3 gap-3">
|
<div className="flex flex-col flex-grow p-3 gap-3">
|
||||||
<DataTable
|
<DataTable
|
||||||
data={data}
|
data={data}
|
||||||
|
|||||||
@@ -1,13 +1,28 @@
|
|||||||
import { cloneDeep, omit } from "lodash-es";
|
import { cloneDeep, omit } from "lodash-es";
|
||||||
|
import { useEffect, useRef } from "react";
|
||||||
import { useBknd } from "ui/client";
|
import { useBknd } from "ui/client";
|
||||||
|
import { useBkndAuth } from "ui/client/schema/auth/use-bknd-auth";
|
||||||
|
import { useBkndData } from "ui/client/schema/data/use-bknd-data";
|
||||||
import { Button } from "ui/components/buttons/Button";
|
import { Button } from "ui/components/buttons/Button";
|
||||||
import { JsonSchemaForm } from "ui/components/form/json-schema/JsonSchemaForm";
|
import { Alert } from "ui/components/display/Alert";
|
||||||
|
import {
|
||||||
|
JsonSchemaForm,
|
||||||
|
type JsonSchemaFormRef
|
||||||
|
} from "ui/components/form/json-schema/JsonSchemaForm";
|
||||||
import * as AppShell from "ui/layouts/AppShell/AppShell";
|
import * as AppShell from "ui/layouts/AppShell/AppShell";
|
||||||
|
import { useNavigate } from "ui/lib/routes";
|
||||||
import { extractSchema } from "../settings/utils/schema";
|
import { extractSchema } from "../settings/utils/schema";
|
||||||
|
|
||||||
|
// @todo: improve the inline editing expierence, for now redirect to settings
|
||||||
export function AuthSettingsList() {
|
export function AuthSettingsList() {
|
||||||
useBknd({ withSecrets: true });
|
const { app } = useBknd();
|
||||||
return <AuthSettingsListInternal />;
|
const [navigate] = useNavigate();
|
||||||
|
useEffect(() => {
|
||||||
|
navigate(app.getSettingsPath(["auth"]));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
/*useBknd({ withSecrets: true });
|
||||||
|
return <AuthSettingsListInternal />;*/
|
||||||
}
|
}
|
||||||
|
|
||||||
const uiSchema = {
|
const uiSchema = {
|
||||||
@@ -21,9 +36,11 @@ const uiSchema = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function AuthSettingsListInternal() {
|
function AuthSettingsListInternal() {
|
||||||
const s = useBknd();
|
const $auth = useBkndAuth();
|
||||||
const config = s.config.auth;
|
const { entities } = useBkndData();
|
||||||
const schema = cloneDeep(omit(s.schema.auth, ["title"]));
|
const formRef = useRef<JsonSchemaFormRef>(null);
|
||||||
|
const config = $auth.config;
|
||||||
|
const schema = cloneDeep(omit($auth.schema, ["title"]));
|
||||||
const [generalSchema, generalConfig, extracted] = extractSchema(schema as any, config, [
|
const [generalSchema, generalConfig, extracted] = extractSchema(schema as any, config, [
|
||||||
"jwt",
|
"jwt",
|
||||||
"roles",
|
"roles",
|
||||||
@@ -32,7 +49,6 @@ function AuthSettingsListInternal() {
|
|||||||
]);
|
]);
|
||||||
try {
|
try {
|
||||||
const user_entity = config.entity_name ?? "users";
|
const user_entity = config.entity_name ?? "users";
|
||||||
const entities = s.config.data.entities ?? {};
|
|
||||||
const user_fields = Object.entries(entities[user_entity]?.fields ?? {})
|
const user_fields = Object.entries(entities[user_entity]?.fields ?? {})
|
||||||
.map(([name, field]) => (!field.config?.virtual ? name : undefined))
|
.map(([name, field]) => (!field.config?.virtual ? name : undefined))
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
@@ -42,18 +58,34 @@ function AuthSettingsListInternal() {
|
|||||||
extracted.jwt.schema.properties.fields.items.enum = user_fields;
|
extracted.jwt.schema.properties.fields.items.enum = user_fields;
|
||||||
extracted.jwt.schema.properties.fields.uniqueItems = true;
|
extracted.jwt.schema.properties.fields.uniqueItems = true;
|
||||||
uiSchema.jwt.fields["ui:widget"] = "checkboxes";
|
uiSchema.jwt.fields["ui:widget"] = "checkboxes";
|
||||||
|
} else {
|
||||||
|
uiSchema.jwt.fields["ui:widget"] = "hidden";
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
console.log({ generalSchema, generalConfig, extracted });
|
|
||||||
|
async function handleSubmit() {
|
||||||
|
console.log(formRef.current?.validateForm(), formRef.current?.formData());
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<AppShell.SectionHeader right={<Button variant="primary">Update</Button>}>
|
<AppShell.SectionHeader
|
||||||
|
right={
|
||||||
|
<Button variant="primary" onClick={handleSubmit}>
|
||||||
|
Update
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
>
|
||||||
Settings
|
Settings
|
||||||
</AppShell.SectionHeader>
|
</AppShell.SectionHeader>
|
||||||
<AppShell.Scrollable>
|
<AppShell.Scrollable>
|
||||||
|
<Alert.Warning
|
||||||
|
visible={!config.enabled}
|
||||||
|
title="Auth not enabled"
|
||||||
|
message="Enable it by toggling the switch below. Please also make sure set a secure secret to sign JWT tokens."
|
||||||
|
/>
|
||||||
<div className="flex flex-col flex-grow px-5 py-4 gap-8">
|
<div className="flex flex-col flex-grow px-5 py-4 gap-8">
|
||||||
<div>
|
<div>
|
||||||
<JsonSchemaForm
|
<JsonSchemaForm
|
||||||
@@ -65,6 +97,7 @@ function AuthSettingsListInternal() {
|
|||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
<h3 className="font-bold">JWT Settings</h3>
|
<h3 className="font-bold">JWT Settings</h3>
|
||||||
<JsonSchemaForm
|
<JsonSchemaForm
|
||||||
|
ref={formRef}
|
||||||
schema={extracted.jwt.schema}
|
schema={extracted.jwt.schema}
|
||||||
uiSchema={uiSchema.jwt}
|
uiSchema={uiSchema.jwt}
|
||||||
className="legacy hide-required-mark fieldset-alternative mute-root"
|
className="legacy hide-required-mark fieldset-alternative mute-root"
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import type { Entity, EntityData } from "data";
|
|||||||
import type { EntityRelation } from "data";
|
import type { EntityRelation } from "data";
|
||||||
import { Fragment, memo, useState } from "react";
|
import { Fragment, memo, useState } from "react";
|
||||||
import { TbArrowLeft, TbDots } from "react-icons/tb";
|
import { TbArrowLeft, TbDots } from "react-icons/tb";
|
||||||
|
import { useBkndData } from "ui/client/schema/data/use-bknd-data";
|
||||||
import { EntityForm } from "ui/modules/data/components/EntityForm";
|
import { EntityForm } from "ui/modules/data/components/EntityForm";
|
||||||
import { EntityTable2 } from "ui/modules/data/components/EntityTable2";
|
import { EntityTable2 } from "ui/modules/data/components/EntityTable2";
|
||||||
import { useEntityForm } from "ui/modules/data/hooks/useEntityForm";
|
import { useEntityForm } from "ui/modules/data/hooks/useEntityForm";
|
||||||
@@ -20,17 +21,17 @@ import { routes, useNavigate } from "../../lib/routes";
|
|||||||
import { bkndModals } from "../../modals";
|
import { bkndModals } from "../../modals";
|
||||||
|
|
||||||
export function DataEntityUpdate({ params }) {
|
export function DataEntityUpdate({ params }) {
|
||||||
const { app } = useBknd();
|
const { $data, relations } = useBkndData();
|
||||||
const entity = app.entity(params.entity as string)!;
|
const entity = $data.entity(params.entity as string)!;
|
||||||
const entityId = Number.parseInt(params.id as string);
|
const entityId = Number.parseInt(params.id as string);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [navigate] = useNavigate();
|
const [navigate] = useNavigate();
|
||||||
useBrowserTitle(["Data", entity.label, `#${entityId}`]);
|
useBrowserTitle(["Data", entity.label, `#${entityId}`]);
|
||||||
const targetRelations = app.relations.listableRelationsOf(entity);
|
const targetRelations = relations.listableRelationsOf(entity);
|
||||||
console.log("targetRelations", targetRelations, app.relations.relationsOf(entity));
|
//console.log("targetRelations", targetRelations, relations.relationsOf(entity));
|
||||||
// filter out polymorphic for now
|
// filter out polymorphic for now
|
||||||
//.filter((r) => r.type() !== "poly");
|
//.filter((r) => r.type() !== "poly");
|
||||||
const local_relation_refs = app.relations
|
const local_relation_refs = relations
|
||||||
.sourceRelationsOf(entity)
|
.sourceRelationsOf(entity)
|
||||||
?.map((r) => r.other(entity).reference);
|
?.map((r) => r.other(entity).reference);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { Type } from "core/utils";
|
import { Type } from "core/utils";
|
||||||
import { querySchema } from "data";
|
import { querySchema } from "data";
|
||||||
import { TbDots } from "react-icons/tb";
|
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 { EntityTable2 } from "ui/modules/data/components/EntityTable2";
|
||||||
import { useBknd } from "../../client";
|
import { useBknd } from "../../client";
|
||||||
import { Button } from "../../components/buttons/Button";
|
import { Button } from "../../components/buttons/Button";
|
||||||
@@ -25,22 +28,21 @@ const searchSchema = Type.Composite(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export function DataEntityList({ params }) {
|
export function DataEntityList({ params }) {
|
||||||
console.log("params", params);
|
const { $data, relations } = useBkndData();
|
||||||
const { app } = useBknd();
|
const entity = $data.entity(params.entity as string);
|
||||||
const entity = app.entity(params.entity as string)!;
|
|
||||||
const [navigate] = useNavigate();
|
const [navigate] = useNavigate();
|
||||||
const search = useSearch(searchSchema, {
|
const search = useSearch(searchSchema, {
|
||||||
select: entity.getSelect(undefined, "table"),
|
select: entity?.getSelect(undefined, "table") ?? [],
|
||||||
sort: entity.getDefaultSort()
|
sort: entity?.getDefaultSort()
|
||||||
});
|
});
|
||||||
console.log("search", search.value);
|
console.log("search", search.value);
|
||||||
useBrowserTitle(["Data", entity.label]);
|
useBrowserTitle(["Data", entity?.label ?? params.entity]);
|
||||||
const PER_PAGE_OPTIONS = [5, 10, 25];
|
const PER_PAGE_OPTIONS = [5, 10, 25];
|
||||||
|
|
||||||
//console.log("search", search.value);
|
//console.log("search", search.value);
|
||||||
|
|
||||||
function handleClickRow(row: Record<string, any>) {
|
function handleClickRow(row: Record<string, any>) {
|
||||||
navigate(routes.data.entity.edit(entity.name, row.id));
|
if (entity) navigate(routes.data.entity.edit(entity.name, row.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleClickPage(page: number) {
|
function handleClickPage(page: number) {
|
||||||
@@ -61,6 +63,10 @@ export function DataEntityList({ params }) {
|
|||||||
search.set("perPage", perPage);
|
search.set("perPage", perPage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!entity) {
|
||||||
|
return <Message.NotFound description={`Entity "${params.entity}" doesn't exist.`} />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<AppShell.SectionHeader
|
<AppShell.SectionHeader
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { omit } from "lodash-es";
|
|||||||
import { type ReactNode, useMemo, useRef, useState } from "react";
|
import { type ReactNode, useMemo, useRef, useState } from "react";
|
||||||
import { TbSettings } from "react-icons/tb";
|
import { TbSettings } from "react-icons/tb";
|
||||||
import { useAuth } from "ui";
|
import { useAuth } from "ui";
|
||||||
|
import { Alert } from "ui/components/display/Alert";
|
||||||
import { Link, Route, useLocation } from "wouter";
|
import { Link, Route, useLocation } from "wouter";
|
||||||
import { useBknd } from "../../../client/BkndProvider";
|
import { useBknd } from "../../../client/BkndProvider";
|
||||||
import { Button } from "../../../components/buttons/Button";
|
import { Button } from "../../../components/buttons/Button";
|
||||||
@@ -36,6 +37,7 @@ export type SettingProps<
|
|||||||
allowDelete?: (config: any) => boolean;
|
allowDelete?: (config: any) => boolean;
|
||||||
allowEdit?: (config: any) => boolean;
|
allowEdit?: (config: any) => boolean;
|
||||||
showAlert?: (config: any) => string | ReactNode | undefined;
|
showAlert?: (config: any) => string | ReactNode | undefined;
|
||||||
|
reloadOnSave?: boolean;
|
||||||
};
|
};
|
||||||
properties?: {
|
properties?: {
|
||||||
[key in keyof Partial<Props>]: {
|
[key in keyof Partial<Props>]: {
|
||||||
@@ -151,6 +153,9 @@ export function Setting<Schema extends TObject = any>({
|
|||||||
|
|
||||||
console.log("save:success", success);
|
console.log("save:success", success);
|
||||||
if (success) {
|
if (success) {
|
||||||
|
if (options?.reloadOnSave) {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
//window.location.reload();
|
//window.location.reload();
|
||||||
} else {
|
} else {
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
@@ -229,11 +234,7 @@ export function Setting<Schema extends TObject = any>({
|
|||||||
<Breadcrumbs path={path} />
|
<Breadcrumbs path={path} />
|
||||||
</AppShell.SectionHeader>
|
</AppShell.SectionHeader>
|
||||||
<AppShell.Scrollable key={path.join("-")}>
|
<AppShell.Scrollable key={path.join("-")}>
|
||||||
{showAlert && (
|
{typeof showAlert === "string" && <Alert.Warning message={showAlert} />}
|
||||||
<div className="flex flex-row dark:bg-amber-300/20 bg-amber-200 p-4">
|
|
||||||
{showAlert}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="flex flex-col flex-grow p-3 gap-3">
|
<div className="flex flex-col flex-grow p-3 gap-3">
|
||||||
<JsonSchemaForm
|
<JsonSchemaForm
|
||||||
|
|||||||
@@ -70,9 +70,11 @@ export const SettingNewModal = ({
|
|||||||
const [module, ...restOfPath] = path;
|
const [module, ...restOfPath] = path;
|
||||||
const addPath = [...restOfPath, newKey].join(".");
|
const addPath = [...restOfPath, newKey].join(".");
|
||||||
if (await actions.add(module as any, addPath, data)) {
|
if (await actions.add(module as any, addPath, data)) {
|
||||||
navigate(prefixPath + newKey, {
|
setTimeout(() => {
|
||||||
replace: true
|
navigate(prefixPath + newKey, {
|
||||||
});
|
replace: true
|
||||||
|
});
|
||||||
|
}, 500);
|
||||||
} else {
|
} else {
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,6 +115,15 @@ export const AuthSettings = ({ schema: _unsafe_copy, config }) => {
|
|||||||
schema={_schema}
|
schema={_schema}
|
||||||
uiSchema={uiSchema}
|
uiSchema={uiSchema}
|
||||||
config={config}
|
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={{
|
properties={{
|
||||||
strategies: {
|
strategies: {
|
||||||
extract: true,
|
extract: true,
|
||||||
|
|||||||
@@ -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 { createClient } from "@libsql/client/node";
|
||||||
import { serveFresh } from "./src/adapter/vite";
|
import { App, type BkndConfig } from "./src";
|
||||||
import { LibsqlConnection } from "./src/data";
|
import { LibsqlConnection } from "./src/data";
|
||||||
import { StorageLocalAdapter } from "./src/media/storage/adapters/StorageLocalAdapter";
|
import { StorageLocalAdapter } from "./src/media/storage/adapters/StorageLocalAdapter";
|
||||||
import { registries } from "./src/modules/registries";
|
import { registries } from "./src/modules/registries";
|
||||||
|
|
||||||
|
async function getHtml() {
|
||||||
|
return readFile("index.html", "utf8");
|
||||||
|
}
|
||||||
|
function addViteScripts(html: string) {
|
||||||
|
return html.replace(
|
||||||
|
"<head>",
|
||||||
|
`<script type="module">
|
||||||
|
import RefreshRuntime from "/@react-refresh"
|
||||||
|
RefreshRuntime.injectIntoGlobalHook(window)
|
||||||
|
window.$RefreshReg$ = () => {}
|
||||||
|
window.$RefreshSig$ = () => (type) => type
|
||||||
|
window.__vite_plugin_react_preamble_installed__ = true
|
||||||
|
</script>
|
||||||
|
<script type="module" src="/@vite/client"></script>
|
||||||
|
`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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", {
|
registries.media.add("local", {
|
||||||
cls: StorageLocalAdapter,
|
cls: StorageLocalAdapter,
|
||||||
schema: StorageLocalAdapter.prototype.getSchema()
|
schema: StorageLocalAdapter.prototype.getSchema()
|
||||||
|
|||||||
Reference in New Issue
Block a user