@dashadmin/dash-admin 1.3.31 → 1.3.44
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/default-theme/menu/AppMenuComponents/expanded/CollapsableSidebarMenu.js +5 -1
- package/dist/default-theme/menu/AppMenuComponents/submenuConstants.js +8 -3
- package/dist/hooks/axios.js +97 -5
- package/dist/providers/authProvider.js +5 -0
- package/package.json +1 -1
- package/src/default-theme/menu/AppMaterialMenu.tsx +1 -0
- package/src/default-theme/menu/AppMenuComponents/expanded/CollapsableSidebarMenu.tsx +5 -1
- package/src/default-theme/menu/AppMenuComponents/submenuConstants.ts +8 -3
- package/src/default-theme/menu/AppSidebarMaterial.tsx +1 -1
- package/src/hooks/axios.tsx +105 -2
- package/src/providers/authProvider.tsx +8 -1
|
@@ -178,7 +178,11 @@ const CollapsableSidebarMenu = ({
|
|
|
178
178
|
primary: /* @__PURE__ */ jsx(Typography, { children: item.label })
|
|
179
179
|
}
|
|
180
180
|
),
|
|
181
|
-
navExpanded &&
|
|
181
|
+
navExpanded && // "expand-icon" is ALSO on the wrapper (not just the inner svg): the row's
|
|
182
|
+
// onClick above does closest('.expand-icon') to tell "clicked the arrow"
|
|
183
|
+
// from "clicked the row" — if only the svg had it, clicking the circle's
|
|
184
|
+
// padding (outside the svg's own box) would miss the check and navigate.
|
|
185
|
+
/* @__PURE__ */ jsx("div", { className: "expand-icon expand-icon-wrapper", onClick: handleExpandClick, children: sidebarPosition === "bottom" ? localOpen ? /* @__PURE__ */ jsx(ExpandMoreOutlinedIcon, { className: "expand-icon more" }) : /* @__PURE__ */ jsx(ExpandLessOutlinedIcon, { className: "expand-icon less" }) : localOpen ? /* @__PURE__ */ jsx(ExpandLessOutlinedIcon, { className: "expand-icon less" }) : /* @__PURE__ */ jsx(ExpandMoreOutlinedIcon, { className: "expand-icon more" }) })
|
|
182
186
|
]
|
|
183
187
|
}
|
|
184
188
|
)
|
|
@@ -26,9 +26,14 @@ const getSubmenuBaseStyle = (sidebarPosition, renderAtTop = false) => ({
|
|
|
26
26
|
overflowY: "auto",
|
|
27
27
|
width: sidebarPosition === "top" || sidebarPosition === "bottom" ? "auto" : "260px",
|
|
28
28
|
minWidth: "200px",
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
// The wrapper is the SINGLE owner of the panel visuals (surface, border,
|
|
30
|
+
// shadow, radius, padding). The inner ul.dropdown must stay transparent —
|
|
31
|
+
// if both layers draw a border/background, their edges show as a seam line.
|
|
32
|
+
background: "var(--primary-contrast, #252526)",
|
|
33
|
+
border: "1px solid rgba(255,255,255,0.08)",
|
|
34
|
+
boxShadow: "0 10px 15px -3px rgba(0,0,0,0.25), 0 4px 6px -4px rgba(0,0,0,0.4)",
|
|
35
|
+
borderRadius: 12,
|
|
36
|
+
padding: 6
|
|
32
37
|
});
|
|
33
38
|
const calculateSubmenuPosition = (rect, sidebarPosition, renderAtTop = false) => {
|
|
34
39
|
switch (sidebarPosition) {
|
package/dist/hooks/axios.js
CHANGED
|
@@ -1,6 +1,39 @@
|
|
|
1
|
+
var __async = (__this, __arguments, generator) => {
|
|
2
|
+
return new Promise((resolve, reject) => {
|
|
3
|
+
var fulfilled = (value) => {
|
|
4
|
+
try {
|
|
5
|
+
step(generator.next(value));
|
|
6
|
+
} catch (e) {
|
|
7
|
+
reject(e);
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
var rejected = (value) => {
|
|
11
|
+
try {
|
|
12
|
+
step(generator.throw(value));
|
|
13
|
+
} catch (e) {
|
|
14
|
+
reject(e);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
|
|
18
|
+
step((generator = generator.apply(__this, __arguments)).next());
|
|
19
|
+
});
|
|
20
|
+
};
|
|
1
21
|
import axios from "axios";
|
|
2
22
|
import constants from "dash-constants/src/DASHAdminSystemConstants";
|
|
3
23
|
import { dashStorage } from "dash-utils";
|
|
24
|
+
let isRefreshing = false;
|
|
25
|
+
let failedQueue = [];
|
|
26
|
+
const processQueue = (error, token) => {
|
|
27
|
+
failedQueue.forEach((prom) => {
|
|
28
|
+
if (error) {
|
|
29
|
+
prom.reject(error);
|
|
30
|
+
} else {
|
|
31
|
+
prom.resolve(token);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
isRefreshing = false;
|
|
35
|
+
failedQueue = [];
|
|
36
|
+
};
|
|
4
37
|
const initAxios = () => {
|
|
5
38
|
let CSRFAuth = false;
|
|
6
39
|
let options = {
|
|
@@ -28,17 +61,76 @@ const initAxios = () => {
|
|
|
28
61
|
(response) => {
|
|
29
62
|
return response;
|
|
30
63
|
},
|
|
31
|
-
(error) => {
|
|
32
|
-
var _a;
|
|
33
|
-
|
|
64
|
+
(error) => __async(null, null, function* () {
|
|
65
|
+
var _a, _b, _c;
|
|
66
|
+
const originalRequest = error.config;
|
|
67
|
+
const authEndpoints = ["/login", "/auth/refresh", "/logout", "/register"];
|
|
68
|
+
const isAuthEndpoint = authEndpoints.some(
|
|
69
|
+
(endpoint) => {
|
|
70
|
+
var _a2;
|
|
71
|
+
return (_a2 = originalRequest.url) == null ? void 0 : _a2.includes(endpoint);
|
|
72
|
+
}
|
|
73
|
+
);
|
|
74
|
+
if (((_a = error.response) == null ? void 0 : _a.status) === 401 && !isAuthEndpoint && !originalRequest._retry) {
|
|
75
|
+
if (isRefreshing) {
|
|
76
|
+
return new Promise((resolve, reject) => {
|
|
77
|
+
failedQueue.push({ resolve, reject });
|
|
78
|
+
}).then((token) => {
|
|
79
|
+
originalRequest.headers.Authorization = "Bearer " + token;
|
|
80
|
+
return instance(originalRequest);
|
|
81
|
+
}).catch((err) => {
|
|
82
|
+
return Promise.reject(err);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
originalRequest._retry = true;
|
|
86
|
+
isRefreshing = true;
|
|
87
|
+
try {
|
|
88
|
+
const refreshToken = dashStorage.getItem("refreshToken");
|
|
89
|
+
if (!refreshToken) {
|
|
90
|
+
yield handleAuthFailure();
|
|
91
|
+
return Promise.reject(error);
|
|
92
|
+
}
|
|
93
|
+
const refreshResponse = yield axios.post(
|
|
94
|
+
`${constants.system.ADMIN_API_URL}/auth/refresh`,
|
|
95
|
+
{ refresh_token: refreshToken }
|
|
96
|
+
);
|
|
97
|
+
const { token, refresh_token } = refreshResponse.data;
|
|
98
|
+
dashStorage.setItem("token", token);
|
|
99
|
+
dashStorage.setItem("refreshToken", refresh_token);
|
|
100
|
+
originalRequest.headers.Authorization = "Bearer " + token;
|
|
101
|
+
processQueue(null, token);
|
|
102
|
+
return instance(originalRequest);
|
|
103
|
+
} catch (refreshError) {
|
|
104
|
+
yield handleAuthFailure();
|
|
105
|
+
processQueue(refreshError, null);
|
|
106
|
+
return Promise.reject(refreshError);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (((_b = error.response) == null ? void 0 : _b.status) !== 401) {
|
|
110
|
+
window.dispatchEvent(new MessageEvent("GlobalError", { data: { error } }));
|
|
111
|
+
}
|
|
34
112
|
if (error.response && error.response.status === 422) {
|
|
35
|
-
return Promise.reject(((
|
|
113
|
+
return Promise.reject(((_c = error.response.data) == null ? void 0 : _c.errors) || error);
|
|
36
114
|
}
|
|
37
115
|
return Promise.reject(error);
|
|
38
|
-
}
|
|
116
|
+
})
|
|
39
117
|
);
|
|
40
118
|
return instance;
|
|
41
119
|
};
|
|
120
|
+
function handleAuthFailure() {
|
|
121
|
+
return __async(this, null, function* () {
|
|
122
|
+
dashStorage.removeItem("token");
|
|
123
|
+
dashStorage.removeItem("refreshToken");
|
|
124
|
+
dashStorage.removeItem("user");
|
|
125
|
+
dashStorage.removeItem("auth");
|
|
126
|
+
dashStorage.removeItem("authenticated");
|
|
127
|
+
dashStorage.removeItem("roles");
|
|
128
|
+
window.dispatchEvent(new MessageEvent("auth:logout"));
|
|
129
|
+
if (!window.location.pathname.includes("/login")) {
|
|
130
|
+
window.location.href = "/login";
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
}
|
|
42
134
|
const useAxios = () => {
|
|
43
135
|
const axios2 = initAxios();
|
|
44
136
|
return {
|
|
@@ -28,6 +28,8 @@ const logoutFromStorage = () => __async(null, null, function* () {
|
|
|
28
28
|
removeCookie("token");
|
|
29
29
|
dashStorage.removeItem("user");
|
|
30
30
|
dashStorage.removeItem("auth");
|
|
31
|
+
dashStorage.removeItem("token");
|
|
32
|
+
dashStorage.removeItem("refreshToken");
|
|
31
33
|
dashStorage.setItem("authenticated", "false");
|
|
32
34
|
dashStorage.setItem("roles", null);
|
|
33
35
|
yield clearDeviceStoreAuth();
|
|
@@ -46,6 +48,9 @@ var authProvider_default = {
|
|
|
46
48
|
if (loginResponse.status >= 200 && loginResponse.status <= 299) {
|
|
47
49
|
setCookie("token", loginResponse.data.token);
|
|
48
50
|
dashStorage.setItem("token", loginResponse.data.token);
|
|
51
|
+
if (loginResponse.data.refresh_token) {
|
|
52
|
+
dashStorage.setItem("refreshToken", loginResponse.data.refresh_token);
|
|
53
|
+
}
|
|
49
54
|
try {
|
|
50
55
|
let { data: auth } = yield axios.get("/auth/getauth");
|
|
51
56
|
dashStorage.setItem("roles", JSON.stringify(auth.user.roles));
|
package/package.json
CHANGED
|
@@ -227,7 +227,11 @@ const CollapsableSidebarMenu = ({
|
|
|
227
227
|
/>
|
|
228
228
|
)}
|
|
229
229
|
{navExpanded && (
|
|
230
|
-
|
|
230
|
+
// "expand-icon" is ALSO on the wrapper (not just the inner svg): the row's
|
|
231
|
+
// onClick above does closest('.expand-icon') to tell "clicked the arrow"
|
|
232
|
+
// from "clicked the row" — if only the svg had it, clicking the circle's
|
|
233
|
+
// padding (outside the svg's own box) would miss the check and navigate.
|
|
234
|
+
<div className="expand-icon expand-icon-wrapper" onClick={handleExpandClick}>
|
|
231
235
|
{/* For bottom position, invert icons since submenu opens upward */}
|
|
232
236
|
{sidebarPosition === "bottom" ? (
|
|
233
237
|
localOpen ?
|
|
@@ -20,9 +20,14 @@ export const getSubmenuBaseStyle = (
|
|
|
20
20
|
overflowY: 'auto',
|
|
21
21
|
width: sidebarPosition === "top" || sidebarPosition === "bottom" ? 'auto' : '260px',
|
|
22
22
|
minWidth: '200px',
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
// The wrapper is the SINGLE owner of the panel visuals (surface, border,
|
|
24
|
+
// shadow, radius, padding). The inner ul.dropdown must stay transparent —
|
|
25
|
+
// if both layers draw a border/background, their edges show as a seam line.
|
|
26
|
+
background: 'var(--primary-contrast, #252526)',
|
|
27
|
+
border: '1px solid rgba(255,255,255,0.08)',
|
|
28
|
+
boxShadow: '0 10px 15px -3px rgba(0,0,0,0.25), 0 4px 6px -4px rgba(0,0,0,0.4)',
|
|
29
|
+
borderRadius: 12,
|
|
30
|
+
padding: 6,
|
|
26
31
|
});
|
|
27
32
|
|
|
28
33
|
/**
|
package/src/hooks/axios.tsx
CHANGED
|
@@ -2,6 +2,23 @@ import axios from 'axios';
|
|
|
2
2
|
import constants from 'dash-constants/src/DASHAdminSystemConstants';
|
|
3
3
|
import { dashStorage } from 'dash-utils';
|
|
4
4
|
|
|
5
|
+
// Track refresh state to prevent multiple simultaneous refresh attempts
|
|
6
|
+
let isRefreshing = false;
|
|
7
|
+
let failedQueue: Array<{ resolve: (token: string) => void; reject: (error: any) => void }> = [];
|
|
8
|
+
|
|
9
|
+
const processQueue = (error: any, token?: string) => {
|
|
10
|
+
failedQueue.forEach(prom => {
|
|
11
|
+
if (error) {
|
|
12
|
+
prom.reject(error);
|
|
13
|
+
} else {
|
|
14
|
+
prom.resolve(token!);
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
isRefreshing = false;
|
|
19
|
+
failedQueue = [];
|
|
20
|
+
};
|
|
21
|
+
|
|
5
22
|
export const initAxios = () => {
|
|
6
23
|
let CSRFAuth = false;
|
|
7
24
|
|
|
@@ -36,8 +53,74 @@ export const initAxios = () => {
|
|
|
36
53
|
//console.log("Axios Success", response);
|
|
37
54
|
return response;
|
|
38
55
|
},
|
|
39
|
-
(error) => {
|
|
40
|
-
|
|
56
|
+
async (error) => {
|
|
57
|
+
const originalRequest = error.config;
|
|
58
|
+
|
|
59
|
+
// Skip refresh for auth-related endpoints to prevent infinite loops
|
|
60
|
+
const authEndpoints = ['/login', '/auth/refresh', '/logout', '/register'];
|
|
61
|
+
const isAuthEndpoint = authEndpoints.some(endpoint =>
|
|
62
|
+
originalRequest.url?.includes(endpoint)
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
// Handle 401 Unauthorized
|
|
66
|
+
if (error.response?.status === 401 && !isAuthEndpoint && !originalRequest._retry) {
|
|
67
|
+
if (isRefreshing) {
|
|
68
|
+
// If already refreshing, queue this request
|
|
69
|
+
return new Promise((resolve, reject) => {
|
|
70
|
+
failedQueue.push({ resolve, reject });
|
|
71
|
+
}).then(token => {
|
|
72
|
+
originalRequest.headers.Authorization = 'Bearer ' + token;
|
|
73
|
+
return instance(originalRequest);
|
|
74
|
+
}).catch(err => {
|
|
75
|
+
return Promise.reject(err);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
originalRequest._retry = true;
|
|
80
|
+
isRefreshing = true;
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
const refreshToken = dashStorage.getItem('refreshToken');
|
|
84
|
+
|
|
85
|
+
if (!refreshToken) {
|
|
86
|
+
// No refresh token available, logout user
|
|
87
|
+
await handleAuthFailure();
|
|
88
|
+
return Promise.reject(error);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Attempt to refresh the access token
|
|
92
|
+
const refreshResponse = await axios.post(
|
|
93
|
+
`${constants.system.ADMIN_API_URL}/auth/refresh`,
|
|
94
|
+
{ refresh_token: refreshToken }
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
const { token, refresh_token } = refreshResponse.data;
|
|
98
|
+
|
|
99
|
+
// Store new tokens
|
|
100
|
+
dashStorage.setItem('token', token);
|
|
101
|
+
dashStorage.setItem('refreshToken', refresh_token);
|
|
102
|
+
|
|
103
|
+
// Update original request with new token
|
|
104
|
+
originalRequest.headers.Authorization = 'Bearer ' + token;
|
|
105
|
+
|
|
106
|
+
// Process queued requests
|
|
107
|
+
processQueue(null, token);
|
|
108
|
+
|
|
109
|
+
// Retry original request
|
|
110
|
+
return instance(originalRequest);
|
|
111
|
+
} catch (refreshError) {
|
|
112
|
+
// Refresh failed, logout user
|
|
113
|
+
await handleAuthFailure();
|
|
114
|
+
processQueue(refreshError, null);
|
|
115
|
+
return Promise.reject(refreshError);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Dispatch global error event for other errors
|
|
120
|
+
if (error.response?.status !== 401) {
|
|
121
|
+
window.dispatchEvent(new MessageEvent('GlobalError', { data: { error } }));
|
|
122
|
+
}
|
|
123
|
+
|
|
41
124
|
if (error.response && error.response.status === 422) {
|
|
42
125
|
/**
|
|
43
126
|
* Error Handling This is important for the system to parse Form field errors within AutoAdmin.
|
|
@@ -45,6 +128,7 @@ export const initAxios = () => {
|
|
|
45
128
|
**/
|
|
46
129
|
return Promise.reject(error.response.data?.errors || error);
|
|
47
130
|
}
|
|
131
|
+
|
|
48
132
|
return Promise.reject(error);
|
|
49
133
|
},
|
|
50
134
|
);
|
|
@@ -52,6 +136,25 @@ export const initAxios = () => {
|
|
|
52
136
|
return instance;
|
|
53
137
|
};
|
|
54
138
|
|
|
139
|
+
// Helper function to handle authentication failure
|
|
140
|
+
async function handleAuthFailure() {
|
|
141
|
+
// Clear auth data
|
|
142
|
+
dashStorage.removeItem('token');
|
|
143
|
+
dashStorage.removeItem('refreshToken');
|
|
144
|
+
dashStorage.removeItem('user');
|
|
145
|
+
dashStorage.removeItem('auth');
|
|
146
|
+
dashStorage.removeItem('authenticated');
|
|
147
|
+
dashStorage.removeItem('roles');
|
|
148
|
+
|
|
149
|
+
// Dispatch logout event
|
|
150
|
+
window.dispatchEvent(new MessageEvent('auth:logout'));
|
|
151
|
+
|
|
152
|
+
// Redirect to login if not already there
|
|
153
|
+
if (!window.location.pathname.includes('/login')) {
|
|
154
|
+
window.location.href = '/login';
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
55
158
|
const useAxios = () => {
|
|
56
159
|
const axios = initAxios();
|
|
57
160
|
return {
|
|
@@ -9,9 +9,11 @@ export const logoutFromStorage = async () => {
|
|
|
9
9
|
removeCookie('token');
|
|
10
10
|
dashStorage.removeItem('user');
|
|
11
11
|
dashStorage.removeItem('auth');
|
|
12
|
+
dashStorage.removeItem('token');
|
|
13
|
+
dashStorage.removeItem('refreshToken'); // Clear refresh token for security
|
|
12
14
|
dashStorage.setItem('authenticated', 'false');
|
|
13
15
|
dashStorage.setItem('roles', null); // before it was guest role.
|
|
14
|
-
|
|
16
|
+
|
|
15
17
|
// Clear auth data from device store (Electron/Capacitor)
|
|
16
18
|
await clearDeviceStoreAuth();
|
|
17
19
|
};
|
|
@@ -40,6 +42,11 @@ export default {
|
|
|
40
42
|
|
|
41
43
|
dashStorage.setItem('token', loginResponse.data.token);
|
|
42
44
|
|
|
45
|
+
// Store refresh token for automatic token refresh
|
|
46
|
+
if (loginResponse.data.refresh_token) {
|
|
47
|
+
dashStorage.setItem('refreshToken', loginResponse.data.refresh_token);
|
|
48
|
+
}
|
|
49
|
+
|
|
43
50
|
try {
|
|
44
51
|
let { data: auth } = await axios.get('/auth/getauth');
|
|
45
52
|
|