@nubitio/admin 0.11.3 → 1.0.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +779 -66
- package/dist/index.d.cts +60 -3
- package/dist/index.d.mts +60 -3
- package/dist/index.mjs +780 -71
- package/package.json +6 -6
package/dist/index.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { BrowserRouter, Link, NavLink, Navigate, Outlet, Route, Routes, useLocation, useNavigate } from "react-router-dom";
|
|
1
|
+
import { BrowserRouter, Link, NavLink, Navigate, Outlet, Route, Routes, useLocation, useNavigate, useParams, useSearchParams } from "react-router-dom";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react";
|
|
4
|
-
import { Badge, Button, Card, EmptyState, FeatureGate as FeatureGate$1, IconButton, TextField, ThemeProvider, ThemeSwitcher, useFloatingPanel } from "@nubitio/ui";
|
|
3
|
+
import React, { Component, createContext, useCallback, useContext, useEffect, useMemo, useState } from "react";
|
|
4
|
+
import { Badge, Button, Card, EmptyState, FeatureGate as FeatureGate$1, FormField, IconButton, Skeleton, TextField, ThemeProvider, ThemeSwitcher, useFloatingPanel } from "@nubitio/ui";
|
|
5
5
|
import { QueryClient, QueryClientProvider, useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
6
6
|
import { CoreConfigProvider, CoreProvider, MercureProvider, useCoreHttpClient, useMercureSubscription } from "@nubitio/core";
|
|
7
|
-
import { DevToolsProvider, SmartCrudRolesProvider, isDevEnvironment, useDevTools } from "@nubitio/crud";
|
|
7
|
+
import { DevToolsProvider, SessionPermissionsProvider, SmartCrudRolesProvider, isDevEnvironment, useDevTools } from "@nubitio/crud";
|
|
8
8
|
import { HydraResourceSchemaProvider, HydraResourceStoreProvider, SchemaProvider } from "@nubitio/hydra";
|
|
9
9
|
//#region packages/admin/FeatureHubLayout.tsx
|
|
10
10
|
function normalizeIcon(icon) {
|
|
@@ -440,14 +440,14 @@ const AdminShell = ({ title, menuItems, headerActions, renderUserMenu, renderThe
|
|
|
440
440
|
//#endregion
|
|
441
441
|
//#region packages/admin/auth/SessionContext.tsx
|
|
442
442
|
const SessionContext = createContext(null);
|
|
443
|
-
function joinApiPath$
|
|
443
|
+
function joinApiPath$7(apiBaseUrl, path) {
|
|
444
444
|
return `${apiBaseUrl.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
|
|
445
445
|
}
|
|
446
446
|
function SessionProvider({ apiBaseUrl = "/api/", mePath = "me", logoutPath = "auth/logout", children }) {
|
|
447
447
|
const [session, setSession] = useState({ status: "loading" });
|
|
448
448
|
const refresh = useCallback(async () => {
|
|
449
449
|
try {
|
|
450
|
-
const response = await fetch(joinApiPath$
|
|
450
|
+
const response = await fetch(joinApiPath$7(apiBaseUrl, mePath), { credentials: "include" });
|
|
451
451
|
if (!response.ok) {
|
|
452
452
|
setSession({ status: "anonymous" });
|
|
453
453
|
return;
|
|
@@ -464,7 +464,7 @@ function SessionProvider({ apiBaseUrl = "/api/", mePath = "me", logoutPath = "au
|
|
|
464
464
|
refresh();
|
|
465
465
|
}, [refresh]);
|
|
466
466
|
const logout = useCallback(async () => {
|
|
467
|
-
await fetch(joinApiPath$
|
|
467
|
+
await fetch(joinApiPath$7(apiBaseUrl, logoutPath), {
|
|
468
468
|
method: "POST",
|
|
469
469
|
credentials: "include"
|
|
470
470
|
});
|
|
@@ -830,12 +830,15 @@ function NotificationPanel({ title = "Notifications", emptyTitle = "You're all c
|
|
|
830
830
|
}
|
|
831
831
|
//#endregion
|
|
832
832
|
//#region packages/admin/auth/LoginPage.tsx
|
|
833
|
-
function joinApiPath$
|
|
833
|
+
function joinApiPath$6(apiBaseUrl, path) {
|
|
834
834
|
return `${apiBaseUrl.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
|
|
835
835
|
}
|
|
836
|
-
|
|
836
|
+
const TOTP_REQUIRED = "second factor required.";
|
|
837
|
+
function LoginPage({ onLoggedIn, apiBaseUrl = "/api/", loginPath = "auth/login", title = "Nubit Admin", hint, defaultUsername = "", oidcProviders = [], forgotPasswordTo = "/forgot-password" }) {
|
|
837
838
|
const [username, setUsername] = useState(defaultUsername);
|
|
838
839
|
const [password, setPassword] = useState("");
|
|
840
|
+
const [totpCode, setTotpCode] = useState("");
|
|
841
|
+
const [needsTotp, setNeedsTotp] = useState(false);
|
|
839
842
|
const [error, setError] = useState(null);
|
|
840
843
|
const [busy, setBusy] = useState(false);
|
|
841
844
|
const submit = async (event) => {
|
|
@@ -843,17 +846,25 @@ function LoginPage({ onLoggedIn, apiBaseUrl = "/api/", loginPath = "auth/login",
|
|
|
843
846
|
setBusy(true);
|
|
844
847
|
setError(null);
|
|
845
848
|
try {
|
|
846
|
-
const
|
|
849
|
+
const body = {
|
|
850
|
+
username,
|
|
851
|
+
password
|
|
852
|
+
};
|
|
853
|
+
if (totpCode !== "") body.totpCode = totpCode;
|
|
854
|
+
const response = await fetch(joinApiPath$6(apiBaseUrl, loginPath), {
|
|
847
855
|
method: "POST",
|
|
848
856
|
headers: { "Content-Type": "application/json" },
|
|
849
857
|
credentials: "include",
|
|
850
|
-
body: JSON.stringify(
|
|
851
|
-
username,
|
|
852
|
-
password
|
|
853
|
-
})
|
|
858
|
+
body: JSON.stringify(body)
|
|
854
859
|
});
|
|
855
860
|
if (!response.ok) {
|
|
856
|
-
|
|
861
|
+
const message = (await response.json().catch(() => null))?.message ?? "Login failed";
|
|
862
|
+
if (message.toLowerCase() === TOTP_REQUIRED) {
|
|
863
|
+
setNeedsTotp(true);
|
|
864
|
+
setError("Enter the code from your authenticator app.");
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
setError(message);
|
|
857
868
|
return;
|
|
858
869
|
}
|
|
859
870
|
onLoggedIn();
|
|
@@ -890,24 +901,41 @@ function LoginPage({ onLoggedIn, apiBaseUrl = "/api/", loginPath = "auth/login",
|
|
|
890
901
|
},
|
|
891
902
|
children: hint
|
|
892
903
|
}),
|
|
893
|
-
/* @__PURE__ */ jsx(
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
904
|
+
/* @__PURE__ */ jsx(FormField, {
|
|
905
|
+
label: "Email",
|
|
906
|
+
children: /* @__PURE__ */ jsx(TextField, {
|
|
907
|
+
placeholder: "Email",
|
|
908
|
+
value: username,
|
|
909
|
+
autoComplete: "username",
|
|
910
|
+
onChange: (e) => setUsername(e.target.value)
|
|
911
|
+
})
|
|
898
912
|
}),
|
|
899
|
-
/* @__PURE__ */ jsx(
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
913
|
+
/* @__PURE__ */ jsx(FormField, {
|
|
914
|
+
label: "Password",
|
|
915
|
+
children: /* @__PURE__ */ jsx(TextField, {
|
|
916
|
+
placeholder: "Password",
|
|
917
|
+
type: "password",
|
|
918
|
+
value: password,
|
|
919
|
+
autoComplete: "current-password",
|
|
920
|
+
onChange: (e) => setPassword(e.target.value)
|
|
921
|
+
})
|
|
922
|
+
}),
|
|
923
|
+
needsTotp && /* @__PURE__ */ jsx(FormField, {
|
|
924
|
+
label: "Authenticator code",
|
|
925
|
+
children: /* @__PURE__ */ jsx(TextField, {
|
|
926
|
+
placeholder: "123456",
|
|
927
|
+
value: totpCode,
|
|
928
|
+
autoComplete: "one-time-code",
|
|
929
|
+
inputMode: "numeric",
|
|
930
|
+
onChange: (e) => setTotpCode(e.target.value)
|
|
931
|
+
})
|
|
905
932
|
}),
|
|
906
933
|
error && /* @__PURE__ */ jsx("p", {
|
|
907
934
|
style: {
|
|
908
935
|
margin: 0,
|
|
909
936
|
color: "var(--error-color, #dc2626)"
|
|
910
937
|
},
|
|
938
|
+
role: "alert",
|
|
911
939
|
children: error
|
|
912
940
|
}),
|
|
913
941
|
/* @__PURE__ */ jsx(Button, {
|
|
@@ -915,12 +943,587 @@ function LoginPage({ onLoggedIn, apiBaseUrl = "/api/", loginPath = "auth/login",
|
|
|
915
943
|
type: "submit",
|
|
916
944
|
disabled: busy,
|
|
917
945
|
children: busy ? "Signing in…" : "Sign in"
|
|
946
|
+
}),
|
|
947
|
+
/* @__PURE__ */ jsx(Link, {
|
|
948
|
+
to: forgotPasswordTo,
|
|
949
|
+
style: { fontSize: "0.875rem" },
|
|
950
|
+
children: "Forgot password?"
|
|
951
|
+
}),
|
|
952
|
+
oidcProviders.map((provider) => /* @__PURE__ */ jsx(Button, {
|
|
953
|
+
variant: "secondary",
|
|
954
|
+
type: "button",
|
|
955
|
+
onClick: () => {
|
|
956
|
+
window.location.assign(joinApiPath$6(apiBaseUrl, `auth/oidc/${provider.id}/redirect`));
|
|
957
|
+
},
|
|
958
|
+
children: provider.label
|
|
959
|
+
}, provider.id))
|
|
960
|
+
]
|
|
961
|
+
}) })
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
//#endregion
|
|
965
|
+
//#region packages/admin/auth/ForgotPasswordPage.tsx
|
|
966
|
+
function joinApiPath$5(apiBaseUrl, path) {
|
|
967
|
+
return `${apiBaseUrl.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
|
|
968
|
+
}
|
|
969
|
+
function ForgotPasswordPage({ apiBaseUrl = "/api/" }) {
|
|
970
|
+
const [username, setUsername] = useState("");
|
|
971
|
+
const [sent, setSent] = useState(false);
|
|
972
|
+
const [busy, setBusy] = useState(false);
|
|
973
|
+
const submit = async (event) => {
|
|
974
|
+
event.preventDefault();
|
|
975
|
+
setBusy(true);
|
|
976
|
+
try {
|
|
977
|
+
await fetch(joinApiPath$5(apiBaseUrl, "auth/password/forgot"), {
|
|
978
|
+
method: "POST",
|
|
979
|
+
headers: { "Content-Type": "application/json" },
|
|
980
|
+
credentials: "include",
|
|
981
|
+
body: JSON.stringify({ username })
|
|
982
|
+
});
|
|
983
|
+
setSent(true);
|
|
984
|
+
} finally {
|
|
985
|
+
setBusy(false);
|
|
986
|
+
}
|
|
987
|
+
};
|
|
988
|
+
return /* @__PURE__ */ jsx("div", {
|
|
989
|
+
style: {
|
|
990
|
+
display: "grid",
|
|
991
|
+
placeItems: "center",
|
|
992
|
+
minHeight: "100vh"
|
|
993
|
+
},
|
|
994
|
+
children: /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsxs("form", {
|
|
995
|
+
onSubmit: submit,
|
|
996
|
+
style: {
|
|
997
|
+
display: "flex",
|
|
998
|
+
flexDirection: "column",
|
|
999
|
+
gap: 12,
|
|
1000
|
+
width: 320,
|
|
1001
|
+
padding: 8
|
|
1002
|
+
},
|
|
1003
|
+
children: [
|
|
1004
|
+
/* @__PURE__ */ jsx("h2", {
|
|
1005
|
+
style: { margin: 0 },
|
|
1006
|
+
children: "Reset password"
|
|
1007
|
+
}),
|
|
1008
|
+
sent ? /* @__PURE__ */ jsx("p", {
|
|
1009
|
+
style: {
|
|
1010
|
+
margin: 0,
|
|
1011
|
+
color: "var(--text-secondary)"
|
|
1012
|
+
},
|
|
1013
|
+
children: "If that address has an account, a reset link is on its way."
|
|
1014
|
+
}) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(FormField, {
|
|
1015
|
+
label: "Email",
|
|
1016
|
+
children: /* @__PURE__ */ jsx(TextField, {
|
|
1017
|
+
value: username,
|
|
1018
|
+
autoComplete: "username",
|
|
1019
|
+
onChange: (e) => setUsername(e.target.value)
|
|
1020
|
+
})
|
|
1021
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
1022
|
+
variant: "primary",
|
|
1023
|
+
type: "submit",
|
|
1024
|
+
disabled: busy || username === "",
|
|
1025
|
+
children: busy ? "Sending…" : "Send reset link"
|
|
1026
|
+
})] }),
|
|
1027
|
+
/* @__PURE__ */ jsx(Link, {
|
|
1028
|
+
to: "/",
|
|
1029
|
+
style: { fontSize: "0.875rem" },
|
|
1030
|
+
children: "Back to sign in"
|
|
918
1031
|
})
|
|
919
1032
|
]
|
|
920
1033
|
}) })
|
|
921
1034
|
});
|
|
922
1035
|
}
|
|
923
1036
|
//#endregion
|
|
1037
|
+
//#region packages/admin/auth/ResetPasswordPage.tsx
|
|
1038
|
+
function joinApiPath$4(apiBaseUrl, path) {
|
|
1039
|
+
return `${apiBaseUrl.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
|
|
1040
|
+
}
|
|
1041
|
+
function ResetPasswordPage({ apiBaseUrl = "/api/" }) {
|
|
1042
|
+
const [params] = useSearchParams();
|
|
1043
|
+
const token = params.get("token") ?? "";
|
|
1044
|
+
const [password, setPassword] = useState("");
|
|
1045
|
+
const [error, setError] = useState(null);
|
|
1046
|
+
const [done, setDone] = useState(false);
|
|
1047
|
+
const [busy, setBusy] = useState(false);
|
|
1048
|
+
const submit = async (event) => {
|
|
1049
|
+
event.preventDefault();
|
|
1050
|
+
setBusy(true);
|
|
1051
|
+
setError(null);
|
|
1052
|
+
try {
|
|
1053
|
+
const response = await fetch(joinApiPath$4(apiBaseUrl, "auth/password/reset"), {
|
|
1054
|
+
method: "POST",
|
|
1055
|
+
headers: { "Content-Type": "application/json" },
|
|
1056
|
+
credentials: "include",
|
|
1057
|
+
body: JSON.stringify({
|
|
1058
|
+
token,
|
|
1059
|
+
password
|
|
1060
|
+
})
|
|
1061
|
+
});
|
|
1062
|
+
if (!response.ok) {
|
|
1063
|
+
setError((await response.json().catch(() => null))?.message ?? "Reset failed");
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
1066
|
+
setDone(true);
|
|
1067
|
+
} catch {
|
|
1068
|
+
setError("Network error");
|
|
1069
|
+
} finally {
|
|
1070
|
+
setBusy(false);
|
|
1071
|
+
}
|
|
1072
|
+
};
|
|
1073
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1074
|
+
style: {
|
|
1075
|
+
display: "grid",
|
|
1076
|
+
placeItems: "center",
|
|
1077
|
+
minHeight: "100vh"
|
|
1078
|
+
},
|
|
1079
|
+
children: /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsxs("form", {
|
|
1080
|
+
onSubmit: submit,
|
|
1081
|
+
style: {
|
|
1082
|
+
display: "flex",
|
|
1083
|
+
flexDirection: "column",
|
|
1084
|
+
gap: 12,
|
|
1085
|
+
width: 320,
|
|
1086
|
+
padding: 8
|
|
1087
|
+
},
|
|
1088
|
+
children: [/* @__PURE__ */ jsx("h2", {
|
|
1089
|
+
style: { margin: 0 },
|
|
1090
|
+
children: "Choose a new password"
|
|
1091
|
+
}), done ? /* @__PURE__ */ jsxs("p", {
|
|
1092
|
+
style: { margin: 0 },
|
|
1093
|
+
children: [
|
|
1094
|
+
"Password updated.",
|
|
1095
|
+
" ",
|
|
1096
|
+
/* @__PURE__ */ jsx(Link, {
|
|
1097
|
+
to: "/",
|
|
1098
|
+
style: { fontSize: "0.875rem" },
|
|
1099
|
+
children: "Sign in"
|
|
1100
|
+
})
|
|
1101
|
+
]
|
|
1102
|
+
}) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1103
|
+
/* @__PURE__ */ jsx(FormField, {
|
|
1104
|
+
label: "New password",
|
|
1105
|
+
children: /* @__PURE__ */ jsx(TextField, {
|
|
1106
|
+
type: "password",
|
|
1107
|
+
value: password,
|
|
1108
|
+
autoComplete: "new-password",
|
|
1109
|
+
minLength: 8,
|
|
1110
|
+
onChange: (e) => setPassword(e.target.value)
|
|
1111
|
+
})
|
|
1112
|
+
}),
|
|
1113
|
+
error && /* @__PURE__ */ jsx("p", {
|
|
1114
|
+
style: {
|
|
1115
|
+
margin: 0,
|
|
1116
|
+
color: "var(--error-color, #dc2626)"
|
|
1117
|
+
},
|
|
1118
|
+
role: "alert",
|
|
1119
|
+
children: error
|
|
1120
|
+
}),
|
|
1121
|
+
/* @__PURE__ */ jsx(Button, {
|
|
1122
|
+
variant: "primary",
|
|
1123
|
+
type: "submit",
|
|
1124
|
+
disabled: busy || token === "" || password.length < 8,
|
|
1125
|
+
children: busy ? "Saving…" : "Update password"
|
|
1126
|
+
})
|
|
1127
|
+
] })]
|
|
1128
|
+
}) })
|
|
1129
|
+
});
|
|
1130
|
+
}
|
|
1131
|
+
//#endregion
|
|
1132
|
+
//#region packages/admin/auth/AcceptInvitationPage.tsx
|
|
1133
|
+
function joinApiPath$3(apiBaseUrl, path) {
|
|
1134
|
+
return `${apiBaseUrl.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
|
|
1135
|
+
}
|
|
1136
|
+
function AcceptInvitationPage({ apiBaseUrl = "/api/" }) {
|
|
1137
|
+
const { token = "" } = useParams();
|
|
1138
|
+
const [email, setEmail] = useState(null);
|
|
1139
|
+
const [password, setPassword] = useState("");
|
|
1140
|
+
const [error, setError] = useState(null);
|
|
1141
|
+
const [done, setDone] = useState(false);
|
|
1142
|
+
const [busy, setBusy] = useState(false);
|
|
1143
|
+
useEffect(() => {
|
|
1144
|
+
let cancelled = false;
|
|
1145
|
+
fetch(joinApiPath$3(apiBaseUrl, `invitations/${token}`), { credentials: "include" }).then(async (response) => {
|
|
1146
|
+
if (cancelled) return;
|
|
1147
|
+
if (!response.ok) {
|
|
1148
|
+
setError("This invitation is no longer valid.");
|
|
1149
|
+
return;
|
|
1150
|
+
}
|
|
1151
|
+
setEmail((await response.json()).email ?? null);
|
|
1152
|
+
}).catch(() => {
|
|
1153
|
+
if (!cancelled) setError("Network error");
|
|
1154
|
+
});
|
|
1155
|
+
return () => {
|
|
1156
|
+
cancelled = true;
|
|
1157
|
+
};
|
|
1158
|
+
}, [apiBaseUrl, token]);
|
|
1159
|
+
const submit = async (event) => {
|
|
1160
|
+
event.preventDefault();
|
|
1161
|
+
setBusy(true);
|
|
1162
|
+
setError(null);
|
|
1163
|
+
try {
|
|
1164
|
+
const response = await fetch(joinApiPath$3(apiBaseUrl, `invitations/${token}/accept`), {
|
|
1165
|
+
method: "POST",
|
|
1166
|
+
headers: { "Content-Type": "application/json" },
|
|
1167
|
+
credentials: "include",
|
|
1168
|
+
body: JSON.stringify({ password })
|
|
1169
|
+
});
|
|
1170
|
+
if (!response.ok) {
|
|
1171
|
+
setError((await response.json().catch(() => null))?.message ?? "Could not accept the invitation");
|
|
1172
|
+
return;
|
|
1173
|
+
}
|
|
1174
|
+
setDone(true);
|
|
1175
|
+
} catch {
|
|
1176
|
+
setError("Network error");
|
|
1177
|
+
} finally {
|
|
1178
|
+
setBusy(false);
|
|
1179
|
+
}
|
|
1180
|
+
};
|
|
1181
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1182
|
+
style: {
|
|
1183
|
+
display: "grid",
|
|
1184
|
+
placeItems: "center",
|
|
1185
|
+
minHeight: "100vh"
|
|
1186
|
+
},
|
|
1187
|
+
children: /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsxs("form", {
|
|
1188
|
+
onSubmit: submit,
|
|
1189
|
+
style: {
|
|
1190
|
+
display: "flex",
|
|
1191
|
+
flexDirection: "column",
|
|
1192
|
+
gap: 12,
|
|
1193
|
+
width: 320,
|
|
1194
|
+
padding: 8
|
|
1195
|
+
},
|
|
1196
|
+
children: [/* @__PURE__ */ jsx("h2", {
|
|
1197
|
+
style: { margin: 0 },
|
|
1198
|
+
children: "Join"
|
|
1199
|
+
}), done ? /* @__PURE__ */ jsxs("p", {
|
|
1200
|
+
style: { margin: 0 },
|
|
1201
|
+
children: [
|
|
1202
|
+
"Account created.",
|
|
1203
|
+
" ",
|
|
1204
|
+
/* @__PURE__ */ jsx(Link, {
|
|
1205
|
+
to: "/",
|
|
1206
|
+
style: { fontSize: "0.875rem" },
|
|
1207
|
+
children: "Sign in"
|
|
1208
|
+
})
|
|
1209
|
+
]
|
|
1210
|
+
}) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1211
|
+
email && /* @__PURE__ */ jsx("p", {
|
|
1212
|
+
style: {
|
|
1213
|
+
margin: 0,
|
|
1214
|
+
color: "var(--text-secondary)"
|
|
1215
|
+
},
|
|
1216
|
+
children: email
|
|
1217
|
+
}),
|
|
1218
|
+
/* @__PURE__ */ jsx(FormField, {
|
|
1219
|
+
label: "Password",
|
|
1220
|
+
children: /* @__PURE__ */ jsx(TextField, {
|
|
1221
|
+
type: "password",
|
|
1222
|
+
value: password,
|
|
1223
|
+
autoComplete: "new-password",
|
|
1224
|
+
minLength: 8,
|
|
1225
|
+
onChange: (e) => setPassword(e.target.value)
|
|
1226
|
+
})
|
|
1227
|
+
}),
|
|
1228
|
+
error && /* @__PURE__ */ jsx("p", {
|
|
1229
|
+
style: {
|
|
1230
|
+
margin: 0,
|
|
1231
|
+
color: "var(--error-color, #dc2626)"
|
|
1232
|
+
},
|
|
1233
|
+
role: "alert",
|
|
1234
|
+
children: error
|
|
1235
|
+
}),
|
|
1236
|
+
/* @__PURE__ */ jsx(Button, {
|
|
1237
|
+
variant: "primary",
|
|
1238
|
+
type: "submit",
|
|
1239
|
+
disabled: busy || password.length < 8,
|
|
1240
|
+
children: busy ? "Creating…" : "Create account"
|
|
1241
|
+
})
|
|
1242
|
+
] })]
|
|
1243
|
+
}) })
|
|
1244
|
+
});
|
|
1245
|
+
}
|
|
1246
|
+
//#endregion
|
|
1247
|
+
//#region packages/admin/auth/AccountPage.tsx
|
|
1248
|
+
function joinApiPath$2(apiBaseUrl, path) {
|
|
1249
|
+
return `${apiBaseUrl.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
|
|
1250
|
+
}
|
|
1251
|
+
function AccountPage({ apiBaseUrl = "/api/" }) {
|
|
1252
|
+
const { username, refresh } = useSession();
|
|
1253
|
+
const [currentPassword, setCurrentPassword] = useState("");
|
|
1254
|
+
const [newPassword, setNewPassword] = useState("");
|
|
1255
|
+
const [passwordMessage, setPasswordMessage] = useState(null);
|
|
1256
|
+
const [totp, setTotp] = useState(null);
|
|
1257
|
+
const [totpCode, setTotpCode] = useState("");
|
|
1258
|
+
const [totpSecret, setTotpSecret] = useState(null);
|
|
1259
|
+
const [totpMessage, setTotpMessage] = useState(null);
|
|
1260
|
+
const [sessions, setSessions] = useState([]);
|
|
1261
|
+
const load = useCallback(async (signal) => {
|
|
1262
|
+
const [totpResponse, sessionResponse] = await Promise.all([fetch(joinApiPath$2(apiBaseUrl, "auth/totp"), {
|
|
1263
|
+
credentials: "include",
|
|
1264
|
+
signal
|
|
1265
|
+
}), fetch(joinApiPath$2(apiBaseUrl, "auth/sessions"), {
|
|
1266
|
+
credentials: "include",
|
|
1267
|
+
signal
|
|
1268
|
+
})]);
|
|
1269
|
+
if (signal?.aborted) return;
|
|
1270
|
+
if (totpResponse.ok) setTotp(await totpResponse.json());
|
|
1271
|
+
if (sessionResponse.ok) setSessions((await sessionResponse.json()).sessions ?? []);
|
|
1272
|
+
}, [apiBaseUrl]);
|
|
1273
|
+
useEffect(() => {
|
|
1274
|
+
const controller = new AbortController();
|
|
1275
|
+
load(controller.signal).catch((error) => {
|
|
1276
|
+
if (!(error instanceof DOMException && error.name === "AbortError")) throw error;
|
|
1277
|
+
});
|
|
1278
|
+
return () => controller.abort();
|
|
1279
|
+
}, [load]);
|
|
1280
|
+
const changePassword = async (event) => {
|
|
1281
|
+
event.preventDefault();
|
|
1282
|
+
setPasswordMessage(null);
|
|
1283
|
+
const response = await fetch(joinApiPath$2(apiBaseUrl, "auth/change-password"), {
|
|
1284
|
+
method: "POST",
|
|
1285
|
+
headers: { "Content-Type": "application/json" },
|
|
1286
|
+
credentials: "include",
|
|
1287
|
+
body: JSON.stringify({
|
|
1288
|
+
currentPassword,
|
|
1289
|
+
newPassword
|
|
1290
|
+
})
|
|
1291
|
+
});
|
|
1292
|
+
if (!response.ok) {
|
|
1293
|
+
setPasswordMessage((await response.json().catch(() => null))?.message ?? "Could not change password");
|
|
1294
|
+
return;
|
|
1295
|
+
}
|
|
1296
|
+
setCurrentPassword("");
|
|
1297
|
+
setNewPassword("");
|
|
1298
|
+
setPasswordMessage("Password updated.");
|
|
1299
|
+
await refresh();
|
|
1300
|
+
};
|
|
1301
|
+
const beginTotp = async () => {
|
|
1302
|
+
setTotpMessage(null);
|
|
1303
|
+
const response = await fetch(joinApiPath$2(apiBaseUrl, "auth/totp"), {
|
|
1304
|
+
method: "POST",
|
|
1305
|
+
credentials: "include"
|
|
1306
|
+
});
|
|
1307
|
+
if (!response.ok) {
|
|
1308
|
+
setTotpMessage((await response.json().catch(() => null))?.message ?? "Could not start enrolment");
|
|
1309
|
+
return;
|
|
1310
|
+
}
|
|
1311
|
+
const payload = await response.json();
|
|
1312
|
+
setTotpSecret(payload.uri ?? payload.secret ?? null);
|
|
1313
|
+
await load();
|
|
1314
|
+
};
|
|
1315
|
+
const confirmTotp = async (event) => {
|
|
1316
|
+
event.preventDefault();
|
|
1317
|
+
const response = await fetch(joinApiPath$2(apiBaseUrl, "auth/totp/confirm"), {
|
|
1318
|
+
method: "POST",
|
|
1319
|
+
headers: { "Content-Type": "application/json" },
|
|
1320
|
+
credentials: "include",
|
|
1321
|
+
body: JSON.stringify({ code: totpCode })
|
|
1322
|
+
});
|
|
1323
|
+
if (!response.ok) {
|
|
1324
|
+
setTotpMessage((await response.json().catch(() => null))?.message ?? "That code is not valid");
|
|
1325
|
+
return;
|
|
1326
|
+
}
|
|
1327
|
+
setTotpCode("");
|
|
1328
|
+
setTotpSecret(null);
|
|
1329
|
+
setTotpMessage("Authenticator enrolled.");
|
|
1330
|
+
await load();
|
|
1331
|
+
};
|
|
1332
|
+
const disableTotp = async (event) => {
|
|
1333
|
+
event.preventDefault();
|
|
1334
|
+
const response = await fetch(joinApiPath$2(apiBaseUrl, "auth/totp"), {
|
|
1335
|
+
method: "DELETE",
|
|
1336
|
+
headers: { "Content-Type": "application/json" },
|
|
1337
|
+
credentials: "include",
|
|
1338
|
+
body: JSON.stringify({ code: totpCode })
|
|
1339
|
+
});
|
|
1340
|
+
if (!response.ok) {
|
|
1341
|
+
setTotpMessage((await response.json().catch(() => null))?.message ?? "Could not disable the second factor");
|
|
1342
|
+
return;
|
|
1343
|
+
}
|
|
1344
|
+
setTotpCode("");
|
|
1345
|
+
setTotpMessage("Second factor removed.");
|
|
1346
|
+
await load();
|
|
1347
|
+
};
|
|
1348
|
+
const revokeSession = async (id) => {
|
|
1349
|
+
await fetch(joinApiPath$2(apiBaseUrl, `auth/sessions/${id}`), {
|
|
1350
|
+
method: "DELETE",
|
|
1351
|
+
credentials: "include"
|
|
1352
|
+
});
|
|
1353
|
+
await load();
|
|
1354
|
+
};
|
|
1355
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1356
|
+
style: {
|
|
1357
|
+
display: "grid",
|
|
1358
|
+
gap: 24,
|
|
1359
|
+
maxWidth: 480,
|
|
1360
|
+
padding: 24
|
|
1361
|
+
},
|
|
1362
|
+
children: [
|
|
1363
|
+
/* @__PURE__ */ jsx("h1", {
|
|
1364
|
+
style: { margin: 0 },
|
|
1365
|
+
children: "Account"
|
|
1366
|
+
}),
|
|
1367
|
+
/* @__PURE__ */ jsx("p", {
|
|
1368
|
+
style: {
|
|
1369
|
+
margin: 0,
|
|
1370
|
+
color: "var(--text-secondary)"
|
|
1371
|
+
},
|
|
1372
|
+
children: username
|
|
1373
|
+
}),
|
|
1374
|
+
/* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsxs("form", {
|
|
1375
|
+
onSubmit: changePassword,
|
|
1376
|
+
style: {
|
|
1377
|
+
display: "flex",
|
|
1378
|
+
flexDirection: "column",
|
|
1379
|
+
gap: 12,
|
|
1380
|
+
padding: 8
|
|
1381
|
+
},
|
|
1382
|
+
children: [
|
|
1383
|
+
/* @__PURE__ */ jsx("h2", {
|
|
1384
|
+
style: {
|
|
1385
|
+
margin: 0,
|
|
1386
|
+
fontSize: "1rem"
|
|
1387
|
+
},
|
|
1388
|
+
children: "Password"
|
|
1389
|
+
}),
|
|
1390
|
+
/* @__PURE__ */ jsx(FormField, {
|
|
1391
|
+
label: "Current password",
|
|
1392
|
+
children: /* @__PURE__ */ jsx(TextField, {
|
|
1393
|
+
type: "password",
|
|
1394
|
+
value: currentPassword,
|
|
1395
|
+
autoComplete: "current-password",
|
|
1396
|
+
onChange: (e) => setCurrentPassword(e.target.value)
|
|
1397
|
+
})
|
|
1398
|
+
}),
|
|
1399
|
+
/* @__PURE__ */ jsx(FormField, {
|
|
1400
|
+
label: "New password",
|
|
1401
|
+
children: /* @__PURE__ */ jsx(TextField, {
|
|
1402
|
+
type: "password",
|
|
1403
|
+
value: newPassword,
|
|
1404
|
+
autoComplete: "new-password",
|
|
1405
|
+
minLength: 8,
|
|
1406
|
+
onChange: (e) => setNewPassword(e.target.value)
|
|
1407
|
+
})
|
|
1408
|
+
}),
|
|
1409
|
+
passwordMessage && /* @__PURE__ */ jsx("p", {
|
|
1410
|
+
style: { margin: 0 },
|
|
1411
|
+
children: passwordMessage
|
|
1412
|
+
}),
|
|
1413
|
+
/* @__PURE__ */ jsx(Button, {
|
|
1414
|
+
variant: "primary",
|
|
1415
|
+
type: "submit",
|
|
1416
|
+
disabled: newPassword.length < 8,
|
|
1417
|
+
children: "Update password"
|
|
1418
|
+
})
|
|
1419
|
+
]
|
|
1420
|
+
}) }),
|
|
1421
|
+
/* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsxs("div", {
|
|
1422
|
+
style: {
|
|
1423
|
+
display: "flex",
|
|
1424
|
+
flexDirection: "column",
|
|
1425
|
+
gap: 12,
|
|
1426
|
+
padding: 8
|
|
1427
|
+
},
|
|
1428
|
+
children: [
|
|
1429
|
+
/* @__PURE__ */ jsx("h2", {
|
|
1430
|
+
style: {
|
|
1431
|
+
margin: 0,
|
|
1432
|
+
fontSize: "1rem"
|
|
1433
|
+
},
|
|
1434
|
+
children: "Authenticator"
|
|
1435
|
+
}),
|
|
1436
|
+
/* @__PURE__ */ jsx("p", {
|
|
1437
|
+
style: {
|
|
1438
|
+
margin: 0,
|
|
1439
|
+
color: "var(--text-secondary)"
|
|
1440
|
+
},
|
|
1441
|
+
children: totp?.enrolled ? "A second factor is enrolled on this account." : "No second factor yet."
|
|
1442
|
+
}),
|
|
1443
|
+
totpSecret && /* @__PURE__ */ jsx("p", {
|
|
1444
|
+
style: {
|
|
1445
|
+
margin: 0,
|
|
1446
|
+
wordBreak: "break-all",
|
|
1447
|
+
fontSize: "0.75rem"
|
|
1448
|
+
},
|
|
1449
|
+
children: totpSecret
|
|
1450
|
+
}),
|
|
1451
|
+
/* @__PURE__ */ jsxs("form", {
|
|
1452
|
+
onSubmit: totp?.enrolled ? disableTotp : totpSecret ? confirmTotp : beginTotp,
|
|
1453
|
+
style: {
|
|
1454
|
+
display: "flex",
|
|
1455
|
+
flexDirection: "column",
|
|
1456
|
+
gap: 12
|
|
1457
|
+
},
|
|
1458
|
+
children: [
|
|
1459
|
+
(totp?.enrolled || totpSecret) && /* @__PURE__ */ jsx(FormField, {
|
|
1460
|
+
label: "Authenticator code",
|
|
1461
|
+
children: /* @__PURE__ */ jsx(TextField, {
|
|
1462
|
+
value: totpCode,
|
|
1463
|
+
autoComplete: "one-time-code",
|
|
1464
|
+
inputMode: "numeric",
|
|
1465
|
+
onChange: (e) => setTotpCode(e.target.value)
|
|
1466
|
+
})
|
|
1467
|
+
}),
|
|
1468
|
+
totpMessage && /* @__PURE__ */ jsx("p", {
|
|
1469
|
+
style: { margin: 0 },
|
|
1470
|
+
children: totpMessage
|
|
1471
|
+
}),
|
|
1472
|
+
/* @__PURE__ */ jsx(Button, {
|
|
1473
|
+
variant: "secondary",
|
|
1474
|
+
type: "submit",
|
|
1475
|
+
children: totp?.enrolled ? "Disable" : totpSecret ? "Confirm" : "Enrol"
|
|
1476
|
+
})
|
|
1477
|
+
]
|
|
1478
|
+
})
|
|
1479
|
+
]
|
|
1480
|
+
}) }),
|
|
1481
|
+
/* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsxs("div", {
|
|
1482
|
+
style: {
|
|
1483
|
+
display: "flex",
|
|
1484
|
+
flexDirection: "column",
|
|
1485
|
+
gap: 12,
|
|
1486
|
+
padding: 8
|
|
1487
|
+
},
|
|
1488
|
+
children: [
|
|
1489
|
+
/* @__PURE__ */ jsx("h2", {
|
|
1490
|
+
style: {
|
|
1491
|
+
margin: 0,
|
|
1492
|
+
fontSize: "1rem"
|
|
1493
|
+
},
|
|
1494
|
+
children: "Sessions"
|
|
1495
|
+
}),
|
|
1496
|
+
sessions.length === 0 && /* @__PURE__ */ jsx("p", {
|
|
1497
|
+
style: {
|
|
1498
|
+
margin: 0,
|
|
1499
|
+
color: "var(--text-secondary)"
|
|
1500
|
+
},
|
|
1501
|
+
children: "No other sessions."
|
|
1502
|
+
}),
|
|
1503
|
+
sessions.map((session) => /* @__PURE__ */ jsxs("div", {
|
|
1504
|
+
style: {
|
|
1505
|
+
display: "flex",
|
|
1506
|
+
justifyContent: "space-between",
|
|
1507
|
+
gap: 12,
|
|
1508
|
+
alignItems: "center"
|
|
1509
|
+
},
|
|
1510
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
1511
|
+
style: { fontSize: "0.875rem" },
|
|
1512
|
+
children: session.lastUsedAt ?? session.createdAt ?? `Session ${session.id}`
|
|
1513
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
1514
|
+
variant: "secondary",
|
|
1515
|
+
size: "sm",
|
|
1516
|
+
type: "button",
|
|
1517
|
+
onClick: () => void revokeSession(session.id),
|
|
1518
|
+
children: "Revoke"
|
|
1519
|
+
})]
|
|
1520
|
+
}, session.id))
|
|
1521
|
+
]
|
|
1522
|
+
}) })
|
|
1523
|
+
]
|
|
1524
|
+
});
|
|
1525
|
+
}
|
|
1526
|
+
//#endregion
|
|
924
1527
|
//#region packages/admin/auth/RegisterPage.tsx
|
|
925
1528
|
function joinApiPath$1(apiBaseUrl, path) {
|
|
926
1529
|
return `${apiBaseUrl.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
|
|
@@ -1288,6 +1891,54 @@ function NubitDevToolsPanel() {
|
|
|
1288
1891
|
});
|
|
1289
1892
|
}
|
|
1290
1893
|
//#endregion
|
|
1894
|
+
//#region packages/admin/app/ErrorBoundary.tsx
|
|
1895
|
+
/**
|
|
1896
|
+
* Keeps a thrown field renderer from taking the whole admin down with it.
|
|
1897
|
+
*/
|
|
1898
|
+
var ErrorBoundary = class extends Component {
|
|
1899
|
+
state = { error: null };
|
|
1900
|
+
static getDerivedStateFromError(error) {
|
|
1901
|
+
return { error };
|
|
1902
|
+
}
|
|
1903
|
+
componentDidCatch(error, info) {
|
|
1904
|
+
console.error("Nubit admin render error", error, info.componentStack);
|
|
1905
|
+
}
|
|
1906
|
+
render() {
|
|
1907
|
+
if (!this.state.error) return this.props.children;
|
|
1908
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1909
|
+
role: "alert",
|
|
1910
|
+
style: {
|
|
1911
|
+
padding: 24,
|
|
1912
|
+
maxWidth: 480
|
|
1913
|
+
},
|
|
1914
|
+
children: [
|
|
1915
|
+
/* @__PURE__ */ jsx("h1", {
|
|
1916
|
+
style: {
|
|
1917
|
+
margin: "0 0 8px",
|
|
1918
|
+
fontSize: "1.25rem"
|
|
1919
|
+
},
|
|
1920
|
+
children: "Something went wrong"
|
|
1921
|
+
}),
|
|
1922
|
+
/* @__PURE__ */ jsx("p", {
|
|
1923
|
+
style: {
|
|
1924
|
+
margin: "0 0 16px",
|
|
1925
|
+
color: "var(--text-secondary)"
|
|
1926
|
+
},
|
|
1927
|
+
children: "Reload the page. If it happens again, the last action is in the browser console."
|
|
1928
|
+
}),
|
|
1929
|
+
/* @__PURE__ */ jsx(Button, {
|
|
1930
|
+
variant: "primary",
|
|
1931
|
+
onClick: () => {
|
|
1932
|
+
this.setState({ error: null });
|
|
1933
|
+
window.location.reload();
|
|
1934
|
+
},
|
|
1935
|
+
children: "Reload"
|
|
1936
|
+
})
|
|
1937
|
+
]
|
|
1938
|
+
});
|
|
1939
|
+
}
|
|
1940
|
+
};
|
|
1941
|
+
//#endregion
|
|
1291
1942
|
//#region packages/admin/app/filterMenuByRoles.ts
|
|
1292
1943
|
function hasAnyRole(required, roles) {
|
|
1293
1944
|
if (required === void 0) return true;
|
|
@@ -1336,20 +1987,28 @@ function defaultUserMenu({ username, close, logout }) {
|
|
|
1336
1987
|
gap: 8,
|
|
1337
1988
|
minWidth: 180
|
|
1338
1989
|
},
|
|
1339
|
-
children: [
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1990
|
+
children: [
|
|
1991
|
+
/* @__PURE__ */ jsx("span", {
|
|
1992
|
+
style: {
|
|
1993
|
+
color: "var(--text-secondary)",
|
|
1994
|
+
fontSize: "0.875rem"
|
|
1995
|
+
},
|
|
1996
|
+
children: username ?? "User"
|
|
1997
|
+
}),
|
|
1998
|
+
/* @__PURE__ */ jsx(Link, {
|
|
1999
|
+
to: "/account",
|
|
2000
|
+
onClick: close,
|
|
2001
|
+
children: "Account"
|
|
2002
|
+
}),
|
|
2003
|
+
/* @__PURE__ */ jsx("button", {
|
|
2004
|
+
type: "button",
|
|
2005
|
+
onClick: () => {
|
|
2006
|
+
close();
|
|
2007
|
+
logout();
|
|
2008
|
+
},
|
|
2009
|
+
children: "Sign out"
|
|
2010
|
+
})
|
|
2011
|
+
]
|
|
1353
2012
|
});
|
|
1354
2013
|
}
|
|
1355
2014
|
function buildMenuContext(session) {
|
|
@@ -1386,7 +2045,25 @@ function NubitAuthenticatedApp({ config }) {
|
|
|
1386
2045
|
const renderThemeSwitcher = config.renderThemeSwitcher ?? (() => /* @__PURE__ */ jsx(ThemeSwitcher, {}));
|
|
1387
2046
|
const renderUserMenu = config.renderUserMenu ?? defaultUserMenu;
|
|
1388
2047
|
const Wrapper = config.Wrapper ?? React.Fragment;
|
|
1389
|
-
|
|
2048
|
+
const profile = session.session.status === "authenticated" ? session.session.profile : void 0;
|
|
2049
|
+
if (session.session.status === "loading") return /* @__PURE__ */ jsxs("div", {
|
|
2050
|
+
style: { padding: 24 },
|
|
2051
|
+
"aria-busy": "true",
|
|
2052
|
+
"aria-label": "Loading session",
|
|
2053
|
+
children: [
|
|
2054
|
+
/* @__PURE__ */ jsx(Skeleton, {
|
|
2055
|
+
variant: "rect",
|
|
2056
|
+
height: 32,
|
|
2057
|
+
width: "33%"
|
|
2058
|
+
}),
|
|
2059
|
+
/* @__PURE__ */ jsx("div", { style: { height: 16 } }),
|
|
2060
|
+
/* @__PURE__ */ jsx(Skeleton, {
|
|
2061
|
+
variant: "rect",
|
|
2062
|
+
height: 256
|
|
2063
|
+
})
|
|
2064
|
+
]
|
|
2065
|
+
});
|
|
2066
|
+
const hasAccountRoute = config.routes.some((route) => route.path === "/account");
|
|
1390
2067
|
const shell = /* @__PURE__ */ jsx(AdminShell, {
|
|
1391
2068
|
title: config.title,
|
|
1392
2069
|
menuItems,
|
|
@@ -1397,54 +2074,86 @@ function NubitAuthenticatedApp({ config }) {
|
|
|
1397
2074
|
...menuContext,
|
|
1398
2075
|
close
|
|
1399
2076
|
}),
|
|
1400
|
-
children: /* @__PURE__ */ jsxs(Routes, { children: [
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
2077
|
+
children: /* @__PURE__ */ jsxs(Routes, { children: [
|
|
2078
|
+
/* @__PURE__ */ jsx(Route, {
|
|
2079
|
+
path: "/",
|
|
2080
|
+
element: /* @__PURE__ */ jsx(Navigate, {
|
|
2081
|
+
to: homePath,
|
|
2082
|
+
replace: true
|
|
2083
|
+
})
|
|
2084
|
+
}),
|
|
2085
|
+
config.routes.map((route) => /* @__PURE__ */ jsx(Route, {
|
|
2086
|
+
path: route.path,
|
|
2087
|
+
element: route.element
|
|
2088
|
+
}, route.path)),
|
|
2089
|
+
!hasAccountRoute && /* @__PURE__ */ jsx(Route, {
|
|
2090
|
+
path: "/account",
|
|
2091
|
+
element: /* @__PURE__ */ jsx(AccountPage, { apiBaseUrl })
|
|
1405
2092
|
})
|
|
1406
|
-
})
|
|
1407
|
-
path: route.path,
|
|
1408
|
-
element: route.element
|
|
1409
|
-
}, route.path))] })
|
|
2093
|
+
] })
|
|
1410
2094
|
});
|
|
1411
2095
|
const devToolsEnabled = config.devTools ?? isDevEnvironment();
|
|
1412
|
-
const authenticated = /* @__PURE__ */ jsx(DevToolsProvider, {
|
|
2096
|
+
const authenticated = /* @__PURE__ */ jsx(ErrorBoundary, { children: /* @__PURE__ */ jsx(DevToolsProvider, {
|
|
1413
2097
|
enabled: devToolsEnabled,
|
|
1414
2098
|
children: /* @__PURE__ */ jsx(CoreProvider, {
|
|
1415
2099
|
http: {
|
|
1416
2100
|
baseUrl: apiBaseUrl,
|
|
1417
2101
|
refreshPath: "auth/refresh",
|
|
1418
|
-
loginPath: "auth/login"
|
|
2102
|
+
loginPath: "auth/login",
|
|
2103
|
+
onUnauthorized: () => {
|
|
2104
|
+
session.logout();
|
|
2105
|
+
}
|
|
1419
2106
|
},
|
|
1420
2107
|
runtime,
|
|
1421
2108
|
children: /* @__PURE__ */ jsx(CoreConfigProvider, {
|
|
1422
2109
|
apiBaseUrl,
|
|
1423
2110
|
locale: config.locale ?? "en",
|
|
1424
|
-
timezone: config.timezone ?? "UTC",
|
|
2111
|
+
timezone: profile?.timeZone ?? config.timezone ?? "UTC",
|
|
1425
2112
|
currency: config.currency ?? "USD",
|
|
1426
|
-
children: /* @__PURE__ */
|
|
2113
|
+
children: /* @__PURE__ */ jsx(SmartCrudRolesProvider, {
|
|
1427
2114
|
roles: session.roles,
|
|
1428
|
-
children:
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
2115
|
+
children: /* @__PURE__ */ jsxs(SessionPermissionsProvider, {
|
|
2116
|
+
permissions: profile?.permissions,
|
|
2117
|
+
limits: profile?.limits,
|
|
2118
|
+
children: [
|
|
2119
|
+
/* @__PURE__ */ jsx(BrowserRouter, { children: /* @__PURE__ */ jsx(Wrapper, { children: config.hydra === false ? shell : /* @__PURE__ */ jsx(MercureProvider, { children: /* @__PURE__ */ jsx(SchemaProvider, { children: /* @__PURE__ */ jsx(HydraResourceSchemaProvider, { children: /* @__PURE__ */ jsx(HydraResourceStoreProvider, { children: shell }) }) }) }) }) }),
|
|
2120
|
+
/* @__PURE__ */ jsx(ToastHost, {
|
|
2121
|
+
toasts,
|
|
2122
|
+
onDismiss: dismiss
|
|
2123
|
+
}),
|
|
2124
|
+
devToolsEnabled && /* @__PURE__ */ jsx(NubitDevToolsPanel, {})
|
|
2125
|
+
]
|
|
2126
|
+
})
|
|
1436
2127
|
})
|
|
1437
2128
|
})
|
|
1438
2129
|
})
|
|
1439
|
-
});
|
|
1440
|
-
if (session.session.status === "authenticated") return authenticated;
|
|
1441
|
-
return /* @__PURE__ */ jsx(BrowserRouter, { children: /* @__PURE__ */ jsx(LoginPage, {
|
|
1442
|
-
apiBaseUrl,
|
|
1443
|
-
title: config.login?.title ?? config.title,
|
|
1444
|
-
hint: config.login?.hint,
|
|
1445
|
-
defaultUsername: config.login?.defaultUsername,
|
|
1446
|
-
onLoggedIn: () => void session.refresh()
|
|
1447
2130
|
}) });
|
|
2131
|
+
if (session.session.status === "authenticated") return authenticated;
|
|
2132
|
+
return /* @__PURE__ */ jsx(BrowserRouter, { children: /* @__PURE__ */ jsxs(Routes, { children: [
|
|
2133
|
+
/* @__PURE__ */ jsx(Route, {
|
|
2134
|
+
path: "/forgot-password",
|
|
2135
|
+
element: /* @__PURE__ */ jsx(ForgotPasswordPage, { apiBaseUrl })
|
|
2136
|
+
}),
|
|
2137
|
+
/* @__PURE__ */ jsx(Route, {
|
|
2138
|
+
path: "/reset-password",
|
|
2139
|
+
element: /* @__PURE__ */ jsx(ResetPasswordPage, { apiBaseUrl })
|
|
2140
|
+
}),
|
|
2141
|
+
/* @__PURE__ */ jsx(Route, {
|
|
2142
|
+
path: "/invitations/:token/accept",
|
|
2143
|
+
element: /* @__PURE__ */ jsx(AcceptInvitationPage, { apiBaseUrl })
|
|
2144
|
+
}),
|
|
2145
|
+
/* @__PURE__ */ jsx(Route, {
|
|
2146
|
+
path: "*",
|
|
2147
|
+
element: /* @__PURE__ */ jsx(LoginPage, {
|
|
2148
|
+
apiBaseUrl,
|
|
2149
|
+
title: config.login?.title ?? config.title,
|
|
2150
|
+
hint: config.login?.hint,
|
|
2151
|
+
defaultUsername: config.login?.defaultUsername,
|
|
2152
|
+
oidcProviders: config.login?.oidcProviders,
|
|
2153
|
+
onLoggedIn: () => void session.refresh()
|
|
2154
|
+
})
|
|
2155
|
+
})
|
|
2156
|
+
] }) });
|
|
1448
2157
|
}
|
|
1449
2158
|
function createNubitApp(config) {
|
|
1450
2159
|
const queryClient = config.queryClient ?? new QueryClient({ defaultOptions: { queries: {
|
|
@@ -1467,4 +2176,4 @@ function createNubitApp(config) {
|
|
|
1467
2176
|
return { App };
|
|
1468
2177
|
}
|
|
1469
2178
|
//#endregion
|
|
1470
|
-
export { AdminHeader, AdminShell, AdminSidebarMenu, FeatureGate, FeatureHubLayout, LoginPage, NotificationPanel, NubitDevToolsPanel, PlanPanel, QuotaUsageBanner, RegisterPage, SessionProvider, StaticSessionProvider, ToastHost, createNubitApp, filterMenuByRoles, hasAnyRole, parseQuotaError, quotaErrorToastMessage, quotaUsageAboveGrid, useAppRuntime, useFeature, useFeatureConfig, useNotifications, useQuotaUsage, useRuntimeConfig, useScreenSize, useScreenSizeClass, useSession };
|
|
2179
|
+
export { AcceptInvitationPage, AccountPage, AdminHeader, AdminShell, AdminSidebarMenu, FeatureGate, FeatureHubLayout, ForgotPasswordPage, LoginPage, NotificationPanel, NubitDevToolsPanel, PlanPanel, QuotaUsageBanner, RegisterPage, ResetPasswordPage, SessionProvider, StaticSessionProvider, ToastHost, createNubitApp, filterMenuByRoles, hasAnyRole, parseQuotaError, quotaErrorToastMessage, quotaUsageAboveGrid, useAppRuntime, useFeature, useFeatureConfig, useNotifications, useQuotaUsage, useRuntimeConfig, useScreenSize, useScreenSizeClass, useSession };
|