@nubitio/admin 0.11.4 → 1.0.0
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.cjs
CHANGED
|
@@ -464,14 +464,14 @@ const AdminShell = ({ title, menuItems, headerActions, renderUserMenu, renderThe
|
|
|
464
464
|
//#endregion
|
|
465
465
|
//#region packages/admin/auth/SessionContext.tsx
|
|
466
466
|
const SessionContext = (0, react.createContext)(null);
|
|
467
|
-
function joinApiPath$
|
|
467
|
+
function joinApiPath$7(apiBaseUrl, path) {
|
|
468
468
|
return `${apiBaseUrl.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
|
|
469
469
|
}
|
|
470
470
|
function SessionProvider({ apiBaseUrl = "/api/", mePath = "me", logoutPath = "auth/logout", children }) {
|
|
471
471
|
const [session, setSession] = (0, react.useState)({ status: "loading" });
|
|
472
472
|
const refresh = (0, react.useCallback)(async () => {
|
|
473
473
|
try {
|
|
474
|
-
const response = await fetch(joinApiPath$
|
|
474
|
+
const response = await fetch(joinApiPath$7(apiBaseUrl, mePath), { credentials: "include" });
|
|
475
475
|
if (!response.ok) {
|
|
476
476
|
setSession({ status: "anonymous" });
|
|
477
477
|
return;
|
|
@@ -488,7 +488,7 @@ function SessionProvider({ apiBaseUrl = "/api/", mePath = "me", logoutPath = "au
|
|
|
488
488
|
refresh();
|
|
489
489
|
}, [refresh]);
|
|
490
490
|
const logout = (0, react.useCallback)(async () => {
|
|
491
|
-
await fetch(joinApiPath$
|
|
491
|
+
await fetch(joinApiPath$7(apiBaseUrl, logoutPath), {
|
|
492
492
|
method: "POST",
|
|
493
493
|
credentials: "include"
|
|
494
494
|
});
|
|
@@ -854,12 +854,15 @@ function NotificationPanel({ title = "Notifications", emptyTitle = "You're all c
|
|
|
854
854
|
}
|
|
855
855
|
//#endregion
|
|
856
856
|
//#region packages/admin/auth/LoginPage.tsx
|
|
857
|
-
function joinApiPath$
|
|
857
|
+
function joinApiPath$6(apiBaseUrl, path) {
|
|
858
858
|
return `${apiBaseUrl.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
|
|
859
859
|
}
|
|
860
|
-
|
|
860
|
+
const TOTP_REQUIRED = "second factor required.";
|
|
861
|
+
function LoginPage({ onLoggedIn, apiBaseUrl = "/api/", loginPath = "auth/login", title = "Nubit Admin", hint, defaultUsername = "", oidcProviders = [], forgotPasswordTo = "/forgot-password" }) {
|
|
861
862
|
const [username, setUsername] = (0, react.useState)(defaultUsername);
|
|
862
863
|
const [password, setPassword] = (0, react.useState)("");
|
|
864
|
+
const [totpCode, setTotpCode] = (0, react.useState)("");
|
|
865
|
+
const [needsTotp, setNeedsTotp] = (0, react.useState)(false);
|
|
863
866
|
const [error, setError] = (0, react.useState)(null);
|
|
864
867
|
const [busy, setBusy] = (0, react.useState)(false);
|
|
865
868
|
const submit = async (event) => {
|
|
@@ -867,17 +870,25 @@ function LoginPage({ onLoggedIn, apiBaseUrl = "/api/", loginPath = "auth/login",
|
|
|
867
870
|
setBusy(true);
|
|
868
871
|
setError(null);
|
|
869
872
|
try {
|
|
870
|
-
const
|
|
873
|
+
const body = {
|
|
874
|
+
username,
|
|
875
|
+
password
|
|
876
|
+
};
|
|
877
|
+
if (totpCode !== "") body.totpCode = totpCode;
|
|
878
|
+
const response = await fetch(joinApiPath$6(apiBaseUrl, loginPath), {
|
|
871
879
|
method: "POST",
|
|
872
880
|
headers: { "Content-Type": "application/json" },
|
|
873
881
|
credentials: "include",
|
|
874
|
-
body: JSON.stringify(
|
|
875
|
-
username,
|
|
876
|
-
password
|
|
877
|
-
})
|
|
882
|
+
body: JSON.stringify(body)
|
|
878
883
|
});
|
|
879
884
|
if (!response.ok) {
|
|
880
|
-
|
|
885
|
+
const message = (await response.json().catch(() => null))?.message ?? "Login failed";
|
|
886
|
+
if (message.toLowerCase() === TOTP_REQUIRED) {
|
|
887
|
+
setNeedsTotp(true);
|
|
888
|
+
setError("Enter the code from your authenticator app.");
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
891
|
+
setError(message);
|
|
881
892
|
return;
|
|
882
893
|
}
|
|
883
894
|
onLoggedIn();
|
|
@@ -914,24 +925,41 @@ function LoginPage({ onLoggedIn, apiBaseUrl = "/api/", loginPath = "auth/login",
|
|
|
914
925
|
},
|
|
915
926
|
children: hint
|
|
916
927
|
}),
|
|
917
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
928
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.FormField, {
|
|
929
|
+
label: "Email",
|
|
930
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.TextField, {
|
|
931
|
+
placeholder: "Email",
|
|
932
|
+
value: username,
|
|
933
|
+
autoComplete: "username",
|
|
934
|
+
onChange: (e) => setUsername(e.target.value)
|
|
935
|
+
})
|
|
922
936
|
}),
|
|
923
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
937
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.FormField, {
|
|
938
|
+
label: "Password",
|
|
939
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.TextField, {
|
|
940
|
+
placeholder: "Password",
|
|
941
|
+
type: "password",
|
|
942
|
+
value: password,
|
|
943
|
+
autoComplete: "current-password",
|
|
944
|
+
onChange: (e) => setPassword(e.target.value)
|
|
945
|
+
})
|
|
946
|
+
}),
|
|
947
|
+
needsTotp && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.FormField, {
|
|
948
|
+
label: "Authenticator code",
|
|
949
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.TextField, {
|
|
950
|
+
placeholder: "123456",
|
|
951
|
+
value: totpCode,
|
|
952
|
+
autoComplete: "one-time-code",
|
|
953
|
+
inputMode: "numeric",
|
|
954
|
+
onChange: (e) => setTotpCode(e.target.value)
|
|
955
|
+
})
|
|
929
956
|
}),
|
|
930
957
|
error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
931
958
|
style: {
|
|
932
959
|
margin: 0,
|
|
933
960
|
color: "var(--error-color, #dc2626)"
|
|
934
961
|
},
|
|
962
|
+
role: "alert",
|
|
935
963
|
children: error
|
|
936
964
|
}),
|
|
937
965
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.Button, {
|
|
@@ -939,12 +967,587 @@ function LoginPage({ onLoggedIn, apiBaseUrl = "/api/", loginPath = "auth/login",
|
|
|
939
967
|
type: "submit",
|
|
940
968
|
disabled: busy,
|
|
941
969
|
children: busy ? "Signing in…" : "Sign in"
|
|
970
|
+
}),
|
|
971
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_router_dom.Link, {
|
|
972
|
+
to: forgotPasswordTo,
|
|
973
|
+
style: { fontSize: "0.875rem" },
|
|
974
|
+
children: "Forgot password?"
|
|
975
|
+
}),
|
|
976
|
+
oidcProviders.map((provider) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.Button, {
|
|
977
|
+
variant: "secondary",
|
|
978
|
+
type: "button",
|
|
979
|
+
onClick: () => {
|
|
980
|
+
window.location.assign(joinApiPath$6(apiBaseUrl, `auth/oidc/${provider.id}/redirect`));
|
|
981
|
+
},
|
|
982
|
+
children: provider.label
|
|
983
|
+
}, provider.id))
|
|
984
|
+
]
|
|
985
|
+
}) })
|
|
986
|
+
});
|
|
987
|
+
}
|
|
988
|
+
//#endregion
|
|
989
|
+
//#region packages/admin/auth/ForgotPasswordPage.tsx
|
|
990
|
+
function joinApiPath$5(apiBaseUrl, path) {
|
|
991
|
+
return `${apiBaseUrl.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
|
|
992
|
+
}
|
|
993
|
+
function ForgotPasswordPage({ apiBaseUrl = "/api/" }) {
|
|
994
|
+
const [username, setUsername] = (0, react.useState)("");
|
|
995
|
+
const [sent, setSent] = (0, react.useState)(false);
|
|
996
|
+
const [busy, setBusy] = (0, react.useState)(false);
|
|
997
|
+
const submit = async (event) => {
|
|
998
|
+
event.preventDefault();
|
|
999
|
+
setBusy(true);
|
|
1000
|
+
try {
|
|
1001
|
+
await fetch(joinApiPath$5(apiBaseUrl, "auth/password/forgot"), {
|
|
1002
|
+
method: "POST",
|
|
1003
|
+
headers: { "Content-Type": "application/json" },
|
|
1004
|
+
credentials: "include",
|
|
1005
|
+
body: JSON.stringify({ username })
|
|
1006
|
+
});
|
|
1007
|
+
setSent(true);
|
|
1008
|
+
} finally {
|
|
1009
|
+
setBusy(false);
|
|
1010
|
+
}
|
|
1011
|
+
};
|
|
1012
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1013
|
+
style: {
|
|
1014
|
+
display: "grid",
|
|
1015
|
+
placeItems: "center",
|
|
1016
|
+
minHeight: "100vh"
|
|
1017
|
+
},
|
|
1018
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.Card, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
|
|
1019
|
+
onSubmit: submit,
|
|
1020
|
+
style: {
|
|
1021
|
+
display: "flex",
|
|
1022
|
+
flexDirection: "column",
|
|
1023
|
+
gap: 12,
|
|
1024
|
+
width: 320,
|
|
1025
|
+
padding: 8
|
|
1026
|
+
},
|
|
1027
|
+
children: [
|
|
1028
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
1029
|
+
style: { margin: 0 },
|
|
1030
|
+
children: "Reset password"
|
|
1031
|
+
}),
|
|
1032
|
+
sent ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1033
|
+
style: {
|
|
1034
|
+
margin: 0,
|
|
1035
|
+
color: "var(--text-secondary)"
|
|
1036
|
+
},
|
|
1037
|
+
children: "If that address has an account, a reset link is on its way."
|
|
1038
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.FormField, {
|
|
1039
|
+
label: "Email",
|
|
1040
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.TextField, {
|
|
1041
|
+
value: username,
|
|
1042
|
+
autoComplete: "username",
|
|
1043
|
+
onChange: (e) => setUsername(e.target.value)
|
|
1044
|
+
})
|
|
1045
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.Button, {
|
|
1046
|
+
variant: "primary",
|
|
1047
|
+
type: "submit",
|
|
1048
|
+
disabled: busy || username === "",
|
|
1049
|
+
children: busy ? "Sending…" : "Send reset link"
|
|
1050
|
+
})] }),
|
|
1051
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_router_dom.Link, {
|
|
1052
|
+
to: "/",
|
|
1053
|
+
style: { fontSize: "0.875rem" },
|
|
1054
|
+
children: "Back to sign in"
|
|
942
1055
|
})
|
|
943
1056
|
]
|
|
944
1057
|
}) })
|
|
945
1058
|
});
|
|
946
1059
|
}
|
|
947
1060
|
//#endregion
|
|
1061
|
+
//#region packages/admin/auth/ResetPasswordPage.tsx
|
|
1062
|
+
function joinApiPath$4(apiBaseUrl, path) {
|
|
1063
|
+
return `${apiBaseUrl.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
|
|
1064
|
+
}
|
|
1065
|
+
function ResetPasswordPage({ apiBaseUrl = "/api/" }) {
|
|
1066
|
+
const [params] = (0, react_router_dom.useSearchParams)();
|
|
1067
|
+
const token = params.get("token") ?? "";
|
|
1068
|
+
const [password, setPassword] = (0, react.useState)("");
|
|
1069
|
+
const [error, setError] = (0, react.useState)(null);
|
|
1070
|
+
const [done, setDone] = (0, react.useState)(false);
|
|
1071
|
+
const [busy, setBusy] = (0, react.useState)(false);
|
|
1072
|
+
const submit = async (event) => {
|
|
1073
|
+
event.preventDefault();
|
|
1074
|
+
setBusy(true);
|
|
1075
|
+
setError(null);
|
|
1076
|
+
try {
|
|
1077
|
+
const response = await fetch(joinApiPath$4(apiBaseUrl, "auth/password/reset"), {
|
|
1078
|
+
method: "POST",
|
|
1079
|
+
headers: { "Content-Type": "application/json" },
|
|
1080
|
+
credentials: "include",
|
|
1081
|
+
body: JSON.stringify({
|
|
1082
|
+
token,
|
|
1083
|
+
password
|
|
1084
|
+
})
|
|
1085
|
+
});
|
|
1086
|
+
if (!response.ok) {
|
|
1087
|
+
setError((await response.json().catch(() => null))?.message ?? "Reset failed");
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
setDone(true);
|
|
1091
|
+
} catch {
|
|
1092
|
+
setError("Network error");
|
|
1093
|
+
} finally {
|
|
1094
|
+
setBusy(false);
|
|
1095
|
+
}
|
|
1096
|
+
};
|
|
1097
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1098
|
+
style: {
|
|
1099
|
+
display: "grid",
|
|
1100
|
+
placeItems: "center",
|
|
1101
|
+
minHeight: "100vh"
|
|
1102
|
+
},
|
|
1103
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.Card, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
|
|
1104
|
+
onSubmit: submit,
|
|
1105
|
+
style: {
|
|
1106
|
+
display: "flex",
|
|
1107
|
+
flexDirection: "column",
|
|
1108
|
+
gap: 12,
|
|
1109
|
+
width: 320,
|
|
1110
|
+
padding: 8
|
|
1111
|
+
},
|
|
1112
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
1113
|
+
style: { margin: 0 },
|
|
1114
|
+
children: "Choose a new password"
|
|
1115
|
+
}), done ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
1116
|
+
style: { margin: 0 },
|
|
1117
|
+
children: [
|
|
1118
|
+
"Password updated.",
|
|
1119
|
+
" ",
|
|
1120
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_router_dom.Link, {
|
|
1121
|
+
to: "/",
|
|
1122
|
+
style: { fontSize: "0.875rem" },
|
|
1123
|
+
children: "Sign in"
|
|
1124
|
+
})
|
|
1125
|
+
]
|
|
1126
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1127
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.FormField, {
|
|
1128
|
+
label: "New password",
|
|
1129
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.TextField, {
|
|
1130
|
+
type: "password",
|
|
1131
|
+
value: password,
|
|
1132
|
+
autoComplete: "new-password",
|
|
1133
|
+
minLength: 8,
|
|
1134
|
+
onChange: (e) => setPassword(e.target.value)
|
|
1135
|
+
})
|
|
1136
|
+
}),
|
|
1137
|
+
error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1138
|
+
style: {
|
|
1139
|
+
margin: 0,
|
|
1140
|
+
color: "var(--error-color, #dc2626)"
|
|
1141
|
+
},
|
|
1142
|
+
role: "alert",
|
|
1143
|
+
children: error
|
|
1144
|
+
}),
|
|
1145
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.Button, {
|
|
1146
|
+
variant: "primary",
|
|
1147
|
+
type: "submit",
|
|
1148
|
+
disabled: busy || token === "" || password.length < 8,
|
|
1149
|
+
children: busy ? "Saving…" : "Update password"
|
|
1150
|
+
})
|
|
1151
|
+
] })]
|
|
1152
|
+
}) })
|
|
1153
|
+
});
|
|
1154
|
+
}
|
|
1155
|
+
//#endregion
|
|
1156
|
+
//#region packages/admin/auth/AcceptInvitationPage.tsx
|
|
1157
|
+
function joinApiPath$3(apiBaseUrl, path) {
|
|
1158
|
+
return `${apiBaseUrl.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
|
|
1159
|
+
}
|
|
1160
|
+
function AcceptInvitationPage({ apiBaseUrl = "/api/" }) {
|
|
1161
|
+
const { token = "" } = (0, react_router_dom.useParams)();
|
|
1162
|
+
const [email, setEmail] = (0, react.useState)(null);
|
|
1163
|
+
const [password, setPassword] = (0, react.useState)("");
|
|
1164
|
+
const [error, setError] = (0, react.useState)(null);
|
|
1165
|
+
const [done, setDone] = (0, react.useState)(false);
|
|
1166
|
+
const [busy, setBusy] = (0, react.useState)(false);
|
|
1167
|
+
(0, react.useEffect)(() => {
|
|
1168
|
+
let cancelled = false;
|
|
1169
|
+
fetch(joinApiPath$3(apiBaseUrl, `invitations/${token}`), { credentials: "include" }).then(async (response) => {
|
|
1170
|
+
if (cancelled) return;
|
|
1171
|
+
if (!response.ok) {
|
|
1172
|
+
setError("This invitation is no longer valid.");
|
|
1173
|
+
return;
|
|
1174
|
+
}
|
|
1175
|
+
setEmail((await response.json()).email ?? null);
|
|
1176
|
+
}).catch(() => {
|
|
1177
|
+
if (!cancelled) setError("Network error");
|
|
1178
|
+
});
|
|
1179
|
+
return () => {
|
|
1180
|
+
cancelled = true;
|
|
1181
|
+
};
|
|
1182
|
+
}, [apiBaseUrl, token]);
|
|
1183
|
+
const submit = async (event) => {
|
|
1184
|
+
event.preventDefault();
|
|
1185
|
+
setBusy(true);
|
|
1186
|
+
setError(null);
|
|
1187
|
+
try {
|
|
1188
|
+
const response = await fetch(joinApiPath$3(apiBaseUrl, `invitations/${token}/accept`), {
|
|
1189
|
+
method: "POST",
|
|
1190
|
+
headers: { "Content-Type": "application/json" },
|
|
1191
|
+
credentials: "include",
|
|
1192
|
+
body: JSON.stringify({ password })
|
|
1193
|
+
});
|
|
1194
|
+
if (!response.ok) {
|
|
1195
|
+
setError((await response.json().catch(() => null))?.message ?? "Could not accept the invitation");
|
|
1196
|
+
return;
|
|
1197
|
+
}
|
|
1198
|
+
setDone(true);
|
|
1199
|
+
} catch {
|
|
1200
|
+
setError("Network error");
|
|
1201
|
+
} finally {
|
|
1202
|
+
setBusy(false);
|
|
1203
|
+
}
|
|
1204
|
+
};
|
|
1205
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1206
|
+
style: {
|
|
1207
|
+
display: "grid",
|
|
1208
|
+
placeItems: "center",
|
|
1209
|
+
minHeight: "100vh"
|
|
1210
|
+
},
|
|
1211
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.Card, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
|
|
1212
|
+
onSubmit: submit,
|
|
1213
|
+
style: {
|
|
1214
|
+
display: "flex",
|
|
1215
|
+
flexDirection: "column",
|
|
1216
|
+
gap: 12,
|
|
1217
|
+
width: 320,
|
|
1218
|
+
padding: 8
|
|
1219
|
+
},
|
|
1220
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
1221
|
+
style: { margin: 0 },
|
|
1222
|
+
children: "Join"
|
|
1223
|
+
}), done ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
1224
|
+
style: { margin: 0 },
|
|
1225
|
+
children: [
|
|
1226
|
+
"Account created.",
|
|
1227
|
+
" ",
|
|
1228
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_router_dom.Link, {
|
|
1229
|
+
to: "/",
|
|
1230
|
+
style: { fontSize: "0.875rem" },
|
|
1231
|
+
children: "Sign in"
|
|
1232
|
+
})
|
|
1233
|
+
]
|
|
1234
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1235
|
+
email && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1236
|
+
style: {
|
|
1237
|
+
margin: 0,
|
|
1238
|
+
color: "var(--text-secondary)"
|
|
1239
|
+
},
|
|
1240
|
+
children: email
|
|
1241
|
+
}),
|
|
1242
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.FormField, {
|
|
1243
|
+
label: "Password",
|
|
1244
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.TextField, {
|
|
1245
|
+
type: "password",
|
|
1246
|
+
value: password,
|
|
1247
|
+
autoComplete: "new-password",
|
|
1248
|
+
minLength: 8,
|
|
1249
|
+
onChange: (e) => setPassword(e.target.value)
|
|
1250
|
+
})
|
|
1251
|
+
}),
|
|
1252
|
+
error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1253
|
+
style: {
|
|
1254
|
+
margin: 0,
|
|
1255
|
+
color: "var(--error-color, #dc2626)"
|
|
1256
|
+
},
|
|
1257
|
+
role: "alert",
|
|
1258
|
+
children: error
|
|
1259
|
+
}),
|
|
1260
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.Button, {
|
|
1261
|
+
variant: "primary",
|
|
1262
|
+
type: "submit",
|
|
1263
|
+
disabled: busy || password.length < 8,
|
|
1264
|
+
children: busy ? "Creating…" : "Create account"
|
|
1265
|
+
})
|
|
1266
|
+
] })]
|
|
1267
|
+
}) })
|
|
1268
|
+
});
|
|
1269
|
+
}
|
|
1270
|
+
//#endregion
|
|
1271
|
+
//#region packages/admin/auth/AccountPage.tsx
|
|
1272
|
+
function joinApiPath$2(apiBaseUrl, path) {
|
|
1273
|
+
return `${apiBaseUrl.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
|
|
1274
|
+
}
|
|
1275
|
+
function AccountPage({ apiBaseUrl = "/api/" }) {
|
|
1276
|
+
const { username, refresh } = useSession();
|
|
1277
|
+
const [currentPassword, setCurrentPassword] = (0, react.useState)("");
|
|
1278
|
+
const [newPassword, setNewPassword] = (0, react.useState)("");
|
|
1279
|
+
const [passwordMessage, setPasswordMessage] = (0, react.useState)(null);
|
|
1280
|
+
const [totp, setTotp] = (0, react.useState)(null);
|
|
1281
|
+
const [totpCode, setTotpCode] = (0, react.useState)("");
|
|
1282
|
+
const [totpSecret, setTotpSecret] = (0, react.useState)(null);
|
|
1283
|
+
const [totpMessage, setTotpMessage] = (0, react.useState)(null);
|
|
1284
|
+
const [sessions, setSessions] = (0, react.useState)([]);
|
|
1285
|
+
const load = (0, react.useCallback)(async (signal) => {
|
|
1286
|
+
const [totpResponse, sessionResponse] = await Promise.all([fetch(joinApiPath$2(apiBaseUrl, "auth/totp"), {
|
|
1287
|
+
credentials: "include",
|
|
1288
|
+
signal
|
|
1289
|
+
}), fetch(joinApiPath$2(apiBaseUrl, "auth/sessions"), {
|
|
1290
|
+
credentials: "include",
|
|
1291
|
+
signal
|
|
1292
|
+
})]);
|
|
1293
|
+
if (signal?.aborted) return;
|
|
1294
|
+
if (totpResponse.ok) setTotp(await totpResponse.json());
|
|
1295
|
+
if (sessionResponse.ok) setSessions((await sessionResponse.json()).sessions ?? []);
|
|
1296
|
+
}, [apiBaseUrl]);
|
|
1297
|
+
(0, react.useEffect)(() => {
|
|
1298
|
+
const controller = new AbortController();
|
|
1299
|
+
load(controller.signal).catch((error) => {
|
|
1300
|
+
if (!(error instanceof DOMException && error.name === "AbortError")) throw error;
|
|
1301
|
+
});
|
|
1302
|
+
return () => controller.abort();
|
|
1303
|
+
}, [load]);
|
|
1304
|
+
const changePassword = async (event) => {
|
|
1305
|
+
event.preventDefault();
|
|
1306
|
+
setPasswordMessage(null);
|
|
1307
|
+
const response = await fetch(joinApiPath$2(apiBaseUrl, "auth/change-password"), {
|
|
1308
|
+
method: "POST",
|
|
1309
|
+
headers: { "Content-Type": "application/json" },
|
|
1310
|
+
credentials: "include",
|
|
1311
|
+
body: JSON.stringify({
|
|
1312
|
+
currentPassword,
|
|
1313
|
+
newPassword
|
|
1314
|
+
})
|
|
1315
|
+
});
|
|
1316
|
+
if (!response.ok) {
|
|
1317
|
+
setPasswordMessage((await response.json().catch(() => null))?.message ?? "Could not change password");
|
|
1318
|
+
return;
|
|
1319
|
+
}
|
|
1320
|
+
setCurrentPassword("");
|
|
1321
|
+
setNewPassword("");
|
|
1322
|
+
setPasswordMessage("Password updated.");
|
|
1323
|
+
await refresh();
|
|
1324
|
+
};
|
|
1325
|
+
const beginTotp = async () => {
|
|
1326
|
+
setTotpMessage(null);
|
|
1327
|
+
const response = await fetch(joinApiPath$2(apiBaseUrl, "auth/totp"), {
|
|
1328
|
+
method: "POST",
|
|
1329
|
+
credentials: "include"
|
|
1330
|
+
});
|
|
1331
|
+
if (!response.ok) {
|
|
1332
|
+
setTotpMessage((await response.json().catch(() => null))?.message ?? "Could not start enrolment");
|
|
1333
|
+
return;
|
|
1334
|
+
}
|
|
1335
|
+
const payload = await response.json();
|
|
1336
|
+
setTotpSecret(payload.uri ?? payload.secret ?? null);
|
|
1337
|
+
await load();
|
|
1338
|
+
};
|
|
1339
|
+
const confirmTotp = async (event) => {
|
|
1340
|
+
event.preventDefault();
|
|
1341
|
+
const response = await fetch(joinApiPath$2(apiBaseUrl, "auth/totp/confirm"), {
|
|
1342
|
+
method: "POST",
|
|
1343
|
+
headers: { "Content-Type": "application/json" },
|
|
1344
|
+
credentials: "include",
|
|
1345
|
+
body: JSON.stringify({ code: totpCode })
|
|
1346
|
+
});
|
|
1347
|
+
if (!response.ok) {
|
|
1348
|
+
setTotpMessage((await response.json().catch(() => null))?.message ?? "That code is not valid");
|
|
1349
|
+
return;
|
|
1350
|
+
}
|
|
1351
|
+
setTotpCode("");
|
|
1352
|
+
setTotpSecret(null);
|
|
1353
|
+
setTotpMessage("Authenticator enrolled.");
|
|
1354
|
+
await load();
|
|
1355
|
+
};
|
|
1356
|
+
const disableTotp = async (event) => {
|
|
1357
|
+
event.preventDefault();
|
|
1358
|
+
const response = await fetch(joinApiPath$2(apiBaseUrl, "auth/totp"), {
|
|
1359
|
+
method: "DELETE",
|
|
1360
|
+
headers: { "Content-Type": "application/json" },
|
|
1361
|
+
credentials: "include",
|
|
1362
|
+
body: JSON.stringify({ code: totpCode })
|
|
1363
|
+
});
|
|
1364
|
+
if (!response.ok) {
|
|
1365
|
+
setTotpMessage((await response.json().catch(() => null))?.message ?? "Could not disable the second factor");
|
|
1366
|
+
return;
|
|
1367
|
+
}
|
|
1368
|
+
setTotpCode("");
|
|
1369
|
+
setTotpMessage("Second factor removed.");
|
|
1370
|
+
await load();
|
|
1371
|
+
};
|
|
1372
|
+
const revokeSession = async (id) => {
|
|
1373
|
+
await fetch(joinApiPath$2(apiBaseUrl, `auth/sessions/${id}`), {
|
|
1374
|
+
method: "DELETE",
|
|
1375
|
+
credentials: "include"
|
|
1376
|
+
});
|
|
1377
|
+
await load();
|
|
1378
|
+
};
|
|
1379
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1380
|
+
style: {
|
|
1381
|
+
display: "grid",
|
|
1382
|
+
gap: 24,
|
|
1383
|
+
maxWidth: 480,
|
|
1384
|
+
padding: 24
|
|
1385
|
+
},
|
|
1386
|
+
children: [
|
|
1387
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h1", {
|
|
1388
|
+
style: { margin: 0 },
|
|
1389
|
+
children: "Account"
|
|
1390
|
+
}),
|
|
1391
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1392
|
+
style: {
|
|
1393
|
+
margin: 0,
|
|
1394
|
+
color: "var(--text-secondary)"
|
|
1395
|
+
},
|
|
1396
|
+
children: username
|
|
1397
|
+
}),
|
|
1398
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.Card, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
|
|
1399
|
+
onSubmit: changePassword,
|
|
1400
|
+
style: {
|
|
1401
|
+
display: "flex",
|
|
1402
|
+
flexDirection: "column",
|
|
1403
|
+
gap: 12,
|
|
1404
|
+
padding: 8
|
|
1405
|
+
},
|
|
1406
|
+
children: [
|
|
1407
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
1408
|
+
style: {
|
|
1409
|
+
margin: 0,
|
|
1410
|
+
fontSize: "1rem"
|
|
1411
|
+
},
|
|
1412
|
+
children: "Password"
|
|
1413
|
+
}),
|
|
1414
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.FormField, {
|
|
1415
|
+
label: "Current password",
|
|
1416
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.TextField, {
|
|
1417
|
+
type: "password",
|
|
1418
|
+
value: currentPassword,
|
|
1419
|
+
autoComplete: "current-password",
|
|
1420
|
+
onChange: (e) => setCurrentPassword(e.target.value)
|
|
1421
|
+
})
|
|
1422
|
+
}),
|
|
1423
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.FormField, {
|
|
1424
|
+
label: "New password",
|
|
1425
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.TextField, {
|
|
1426
|
+
type: "password",
|
|
1427
|
+
value: newPassword,
|
|
1428
|
+
autoComplete: "new-password",
|
|
1429
|
+
minLength: 8,
|
|
1430
|
+
onChange: (e) => setNewPassword(e.target.value)
|
|
1431
|
+
})
|
|
1432
|
+
}),
|
|
1433
|
+
passwordMessage && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1434
|
+
style: { margin: 0 },
|
|
1435
|
+
children: passwordMessage
|
|
1436
|
+
}),
|
|
1437
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.Button, {
|
|
1438
|
+
variant: "primary",
|
|
1439
|
+
type: "submit",
|
|
1440
|
+
disabled: newPassword.length < 8,
|
|
1441
|
+
children: "Update password"
|
|
1442
|
+
})
|
|
1443
|
+
]
|
|
1444
|
+
}) }),
|
|
1445
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.Card, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1446
|
+
style: {
|
|
1447
|
+
display: "flex",
|
|
1448
|
+
flexDirection: "column",
|
|
1449
|
+
gap: 12,
|
|
1450
|
+
padding: 8
|
|
1451
|
+
},
|
|
1452
|
+
children: [
|
|
1453
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
1454
|
+
style: {
|
|
1455
|
+
margin: 0,
|
|
1456
|
+
fontSize: "1rem"
|
|
1457
|
+
},
|
|
1458
|
+
children: "Authenticator"
|
|
1459
|
+
}),
|
|
1460
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1461
|
+
style: {
|
|
1462
|
+
margin: 0,
|
|
1463
|
+
color: "var(--text-secondary)"
|
|
1464
|
+
},
|
|
1465
|
+
children: totp?.enrolled ? "A second factor is enrolled on this account." : "No second factor yet."
|
|
1466
|
+
}),
|
|
1467
|
+
totpSecret && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1468
|
+
style: {
|
|
1469
|
+
margin: 0,
|
|
1470
|
+
wordBreak: "break-all",
|
|
1471
|
+
fontSize: "0.75rem"
|
|
1472
|
+
},
|
|
1473
|
+
children: totpSecret
|
|
1474
|
+
}),
|
|
1475
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
|
|
1476
|
+
onSubmit: totp?.enrolled ? disableTotp : totpSecret ? confirmTotp : beginTotp,
|
|
1477
|
+
style: {
|
|
1478
|
+
display: "flex",
|
|
1479
|
+
flexDirection: "column",
|
|
1480
|
+
gap: 12
|
|
1481
|
+
},
|
|
1482
|
+
children: [
|
|
1483
|
+
(totp?.enrolled || totpSecret) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.FormField, {
|
|
1484
|
+
label: "Authenticator code",
|
|
1485
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.TextField, {
|
|
1486
|
+
value: totpCode,
|
|
1487
|
+
autoComplete: "one-time-code",
|
|
1488
|
+
inputMode: "numeric",
|
|
1489
|
+
onChange: (e) => setTotpCode(e.target.value)
|
|
1490
|
+
})
|
|
1491
|
+
}),
|
|
1492
|
+
totpMessage && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1493
|
+
style: { margin: 0 },
|
|
1494
|
+
children: totpMessage
|
|
1495
|
+
}),
|
|
1496
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.Button, {
|
|
1497
|
+
variant: "secondary",
|
|
1498
|
+
type: "submit",
|
|
1499
|
+
children: totp?.enrolled ? "Disable" : totpSecret ? "Confirm" : "Enrol"
|
|
1500
|
+
})
|
|
1501
|
+
]
|
|
1502
|
+
})
|
|
1503
|
+
]
|
|
1504
|
+
}) }),
|
|
1505
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.Card, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1506
|
+
style: {
|
|
1507
|
+
display: "flex",
|
|
1508
|
+
flexDirection: "column",
|
|
1509
|
+
gap: 12,
|
|
1510
|
+
padding: 8
|
|
1511
|
+
},
|
|
1512
|
+
children: [
|
|
1513
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
1514
|
+
style: {
|
|
1515
|
+
margin: 0,
|
|
1516
|
+
fontSize: "1rem"
|
|
1517
|
+
},
|
|
1518
|
+
children: "Sessions"
|
|
1519
|
+
}),
|
|
1520
|
+
sessions.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1521
|
+
style: {
|
|
1522
|
+
margin: 0,
|
|
1523
|
+
color: "var(--text-secondary)"
|
|
1524
|
+
},
|
|
1525
|
+
children: "No other sessions."
|
|
1526
|
+
}),
|
|
1527
|
+
sessions.map((session) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1528
|
+
style: {
|
|
1529
|
+
display: "flex",
|
|
1530
|
+
justifyContent: "space-between",
|
|
1531
|
+
gap: 12,
|
|
1532
|
+
alignItems: "center"
|
|
1533
|
+
},
|
|
1534
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1535
|
+
style: { fontSize: "0.875rem" },
|
|
1536
|
+
children: session.lastUsedAt ?? session.createdAt ?? `Session ${session.id}`
|
|
1537
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.Button, {
|
|
1538
|
+
variant: "secondary",
|
|
1539
|
+
size: "sm",
|
|
1540
|
+
type: "button",
|
|
1541
|
+
onClick: () => void revokeSession(session.id),
|
|
1542
|
+
children: "Revoke"
|
|
1543
|
+
})]
|
|
1544
|
+
}, session.id))
|
|
1545
|
+
]
|
|
1546
|
+
}) })
|
|
1547
|
+
]
|
|
1548
|
+
});
|
|
1549
|
+
}
|
|
1550
|
+
//#endregion
|
|
948
1551
|
//#region packages/admin/auth/RegisterPage.tsx
|
|
949
1552
|
function joinApiPath$1(apiBaseUrl, path) {
|
|
950
1553
|
return `${apiBaseUrl.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
|
|
@@ -1312,6 +1915,54 @@ function NubitDevToolsPanel() {
|
|
|
1312
1915
|
});
|
|
1313
1916
|
}
|
|
1314
1917
|
//#endregion
|
|
1918
|
+
//#region packages/admin/app/ErrorBoundary.tsx
|
|
1919
|
+
/**
|
|
1920
|
+
* Keeps a thrown field renderer from taking the whole admin down with it.
|
|
1921
|
+
*/
|
|
1922
|
+
var ErrorBoundary = class extends react.Component {
|
|
1923
|
+
state = { error: null };
|
|
1924
|
+
static getDerivedStateFromError(error) {
|
|
1925
|
+
return { error };
|
|
1926
|
+
}
|
|
1927
|
+
componentDidCatch(error, info) {
|
|
1928
|
+
console.error("Nubit admin render error", error, info.componentStack);
|
|
1929
|
+
}
|
|
1930
|
+
render() {
|
|
1931
|
+
if (!this.state.error) return this.props.children;
|
|
1932
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1933
|
+
role: "alert",
|
|
1934
|
+
style: {
|
|
1935
|
+
padding: 24,
|
|
1936
|
+
maxWidth: 480
|
|
1937
|
+
},
|
|
1938
|
+
children: [
|
|
1939
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h1", {
|
|
1940
|
+
style: {
|
|
1941
|
+
margin: "0 0 8px",
|
|
1942
|
+
fontSize: "1.25rem"
|
|
1943
|
+
},
|
|
1944
|
+
children: "Something went wrong"
|
|
1945
|
+
}),
|
|
1946
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1947
|
+
style: {
|
|
1948
|
+
margin: "0 0 16px",
|
|
1949
|
+
color: "var(--text-secondary)"
|
|
1950
|
+
},
|
|
1951
|
+
children: "Reload the page. If it happens again, the last action is in the browser console."
|
|
1952
|
+
}),
|
|
1953
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.Button, {
|
|
1954
|
+
variant: "primary",
|
|
1955
|
+
onClick: () => {
|
|
1956
|
+
this.setState({ error: null });
|
|
1957
|
+
window.location.reload();
|
|
1958
|
+
},
|
|
1959
|
+
children: "Reload"
|
|
1960
|
+
})
|
|
1961
|
+
]
|
|
1962
|
+
});
|
|
1963
|
+
}
|
|
1964
|
+
};
|
|
1965
|
+
//#endregion
|
|
1315
1966
|
//#region packages/admin/app/filterMenuByRoles.ts
|
|
1316
1967
|
function hasAnyRole(required, roles) {
|
|
1317
1968
|
if (required === void 0) return true;
|
|
@@ -1360,20 +2011,28 @@ function defaultUserMenu({ username, close, logout }) {
|
|
|
1360
2011
|
gap: 8,
|
|
1361
2012
|
minWidth: 180
|
|
1362
2013
|
},
|
|
1363
|
-
children: [
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
2014
|
+
children: [
|
|
2015
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2016
|
+
style: {
|
|
2017
|
+
color: "var(--text-secondary)",
|
|
2018
|
+
fontSize: "0.875rem"
|
|
2019
|
+
},
|
|
2020
|
+
children: username ?? "User"
|
|
2021
|
+
}),
|
|
2022
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_router_dom.Link, {
|
|
2023
|
+
to: "/account",
|
|
2024
|
+
onClick: close,
|
|
2025
|
+
children: "Account"
|
|
2026
|
+
}),
|
|
2027
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2028
|
+
type: "button",
|
|
2029
|
+
onClick: () => {
|
|
2030
|
+
close();
|
|
2031
|
+
logout();
|
|
2032
|
+
},
|
|
2033
|
+
children: "Sign out"
|
|
2034
|
+
})
|
|
2035
|
+
]
|
|
1377
2036
|
});
|
|
1378
2037
|
}
|
|
1379
2038
|
function buildMenuContext(session) {
|
|
@@ -1410,7 +2069,25 @@ function NubitAuthenticatedApp({ config }) {
|
|
|
1410
2069
|
const renderThemeSwitcher = config.renderThemeSwitcher ?? (() => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.ThemeSwitcher, {}));
|
|
1411
2070
|
const renderUserMenu = config.renderUserMenu ?? defaultUserMenu;
|
|
1412
2071
|
const Wrapper = config.Wrapper ?? react.default.Fragment;
|
|
1413
|
-
|
|
2072
|
+
const profile = session.session.status === "authenticated" ? session.session.profile : void 0;
|
|
2073
|
+
if (session.session.status === "loading") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2074
|
+
style: { padding: 24 },
|
|
2075
|
+
"aria-busy": "true",
|
|
2076
|
+
"aria-label": "Loading session",
|
|
2077
|
+
children: [
|
|
2078
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.Skeleton, {
|
|
2079
|
+
variant: "rect",
|
|
2080
|
+
height: 32,
|
|
2081
|
+
width: "33%"
|
|
2082
|
+
}),
|
|
2083
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: { height: 16 } }),
|
|
2084
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_ui.Skeleton, {
|
|
2085
|
+
variant: "rect",
|
|
2086
|
+
height: 256
|
|
2087
|
+
})
|
|
2088
|
+
]
|
|
2089
|
+
});
|
|
2090
|
+
const hasAccountRoute = config.routes.some((route) => route.path === "/account");
|
|
1414
2091
|
const shell = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AdminShell, {
|
|
1415
2092
|
title: config.title,
|
|
1416
2093
|
menuItems,
|
|
@@ -1421,54 +2098,86 @@ function NubitAuthenticatedApp({ config }) {
|
|
|
1421
2098
|
...menuContext,
|
|
1422
2099
|
close
|
|
1423
2100
|
}),
|
|
1424
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_router_dom.Routes, { children: [
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
2101
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_router_dom.Routes, { children: [
|
|
2102
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_router_dom.Route, {
|
|
2103
|
+
path: "/",
|
|
2104
|
+
element: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_router_dom.Navigate, {
|
|
2105
|
+
to: homePath,
|
|
2106
|
+
replace: true
|
|
2107
|
+
})
|
|
2108
|
+
}),
|
|
2109
|
+
config.routes.map((route) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_router_dom.Route, {
|
|
2110
|
+
path: route.path,
|
|
2111
|
+
element: route.element
|
|
2112
|
+
}, route.path)),
|
|
2113
|
+
!hasAccountRoute && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_router_dom.Route, {
|
|
2114
|
+
path: "/account",
|
|
2115
|
+
element: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountPage, { apiBaseUrl })
|
|
1429
2116
|
})
|
|
1430
|
-
})
|
|
1431
|
-
path: route.path,
|
|
1432
|
-
element: route.element
|
|
1433
|
-
}, route.path))] })
|
|
2117
|
+
] })
|
|
1434
2118
|
});
|
|
1435
2119
|
const devToolsEnabled = config.devTools ?? (0, _nubitio_crud.isDevEnvironment)();
|
|
1436
|
-
const authenticated = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_crud.DevToolsProvider, {
|
|
2120
|
+
const authenticated = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ErrorBoundary, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_crud.DevToolsProvider, {
|
|
1437
2121
|
enabled: devToolsEnabled,
|
|
1438
2122
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_core.CoreProvider, {
|
|
1439
2123
|
http: {
|
|
1440
2124
|
baseUrl: apiBaseUrl,
|
|
1441
2125
|
refreshPath: "auth/refresh",
|
|
1442
|
-
loginPath: "auth/login"
|
|
2126
|
+
loginPath: "auth/login",
|
|
2127
|
+
onUnauthorized: () => {
|
|
2128
|
+
session.logout();
|
|
2129
|
+
}
|
|
1443
2130
|
},
|
|
1444
2131
|
runtime,
|
|
1445
2132
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_core.CoreConfigProvider, {
|
|
1446
2133
|
apiBaseUrl,
|
|
1447
2134
|
locale: config.locale ?? "en",
|
|
1448
|
-
timezone: config.timezone ?? "UTC",
|
|
2135
|
+
timezone: profile?.timeZone ?? config.timezone ?? "UTC",
|
|
1449
2136
|
currency: config.currency ?? "USD",
|
|
1450
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.
|
|
2137
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_crud.SmartCrudRolesProvider, {
|
|
1451
2138
|
roles: session.roles,
|
|
1452
|
-
children:
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
2139
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_nubitio_crud.SessionPermissionsProvider, {
|
|
2140
|
+
permissions: profile?.permissions,
|
|
2141
|
+
limits: profile?.limits,
|
|
2142
|
+
children: [
|
|
2143
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_router_dom.BrowserRouter, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Wrapper, { children: config.hydra === false ? shell : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_core.MercureProvider, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_hydra.SchemaProvider, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_hydra.HydraResourceSchemaProvider, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_nubitio_hydra.HydraResourceStoreProvider, { children: shell }) }) }) }) }) }),
|
|
2144
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToastHost, {
|
|
2145
|
+
toasts,
|
|
2146
|
+
onDismiss: dismiss
|
|
2147
|
+
}),
|
|
2148
|
+
devToolsEnabled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NubitDevToolsPanel, {})
|
|
2149
|
+
]
|
|
2150
|
+
})
|
|
1460
2151
|
})
|
|
1461
2152
|
})
|
|
1462
2153
|
})
|
|
1463
|
-
});
|
|
1464
|
-
if (session.session.status === "authenticated") return authenticated;
|
|
1465
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_router_dom.BrowserRouter, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LoginPage, {
|
|
1466
|
-
apiBaseUrl,
|
|
1467
|
-
title: config.login?.title ?? config.title,
|
|
1468
|
-
hint: config.login?.hint,
|
|
1469
|
-
defaultUsername: config.login?.defaultUsername,
|
|
1470
|
-
onLoggedIn: () => void session.refresh()
|
|
1471
2154
|
}) });
|
|
2155
|
+
if (session.session.status === "authenticated") return authenticated;
|
|
2156
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_router_dom.BrowserRouter, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_router_dom.Routes, { children: [
|
|
2157
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_router_dom.Route, {
|
|
2158
|
+
path: "/forgot-password",
|
|
2159
|
+
element: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ForgotPasswordPage, { apiBaseUrl })
|
|
2160
|
+
}),
|
|
2161
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_router_dom.Route, {
|
|
2162
|
+
path: "/reset-password",
|
|
2163
|
+
element: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ResetPasswordPage, { apiBaseUrl })
|
|
2164
|
+
}),
|
|
2165
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_router_dom.Route, {
|
|
2166
|
+
path: "/invitations/:token/accept",
|
|
2167
|
+
element: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AcceptInvitationPage, { apiBaseUrl })
|
|
2168
|
+
}),
|
|
2169
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_router_dom.Route, {
|
|
2170
|
+
path: "*",
|
|
2171
|
+
element: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LoginPage, {
|
|
2172
|
+
apiBaseUrl,
|
|
2173
|
+
title: config.login?.title ?? config.title,
|
|
2174
|
+
hint: config.login?.hint,
|
|
2175
|
+
defaultUsername: config.login?.defaultUsername,
|
|
2176
|
+
oidcProviders: config.login?.oidcProviders,
|
|
2177
|
+
onLoggedIn: () => void session.refresh()
|
|
2178
|
+
})
|
|
2179
|
+
})
|
|
2180
|
+
] }) });
|
|
1472
2181
|
}
|
|
1473
2182
|
function createNubitApp(config) {
|
|
1474
2183
|
const queryClient = config.queryClient ?? new _tanstack_react_query.QueryClient({ defaultOptions: { queries: {
|
|
@@ -1491,17 +2200,21 @@ function createNubitApp(config) {
|
|
|
1491
2200
|
return { App };
|
|
1492
2201
|
}
|
|
1493
2202
|
//#endregion
|
|
2203
|
+
exports.AcceptInvitationPage = AcceptInvitationPage;
|
|
2204
|
+
exports.AccountPage = AccountPage;
|
|
1494
2205
|
exports.AdminHeader = AdminHeader;
|
|
1495
2206
|
exports.AdminShell = AdminShell;
|
|
1496
2207
|
exports.AdminSidebarMenu = AdminSidebarMenu;
|
|
1497
2208
|
exports.FeatureGate = FeatureGate;
|
|
1498
2209
|
exports.FeatureHubLayout = FeatureHubLayout;
|
|
2210
|
+
exports.ForgotPasswordPage = ForgotPasswordPage;
|
|
1499
2211
|
exports.LoginPage = LoginPage;
|
|
1500
2212
|
exports.NotificationPanel = NotificationPanel;
|
|
1501
2213
|
exports.NubitDevToolsPanel = NubitDevToolsPanel;
|
|
1502
2214
|
exports.PlanPanel = PlanPanel;
|
|
1503
2215
|
exports.QuotaUsageBanner = QuotaUsageBanner;
|
|
1504
2216
|
exports.RegisterPage = RegisterPage;
|
|
2217
|
+
exports.ResetPasswordPage = ResetPasswordPage;
|
|
1505
2218
|
exports.SessionProvider = SessionProvider;
|
|
1506
2219
|
exports.StaticSessionProvider = StaticSessionProvider;
|
|
1507
2220
|
exports.ToastHost = ToastHost;
|