Skip to content

Commit 160863b

Browse files
authored
Merge pull request #396 from devforth/next
Next
2 parents fe4a281 + c53862e commit 160863b

File tree

8 files changed

+344
-91
lines changed

8 files changed

+344
-91
lines changed

adminforth/documentation/docs/tutorial/03-Customization/08-pageInjections.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,55 @@ new AdminForth({
556556
557557
If you hide the logo with `showBrandLogoInSidebar: false`, components injected via `sidebarTop` will take the whole line width.
558558
559+
## Injection order
560+
561+
Most of injections accept an array of components. By defult the order of components is the same as in the array. You can use standard array methods e.g. `push`, `unshift`, `splice` to put item in desired place.
562+
563+
However, if you want to control the order of injections dynamically, which is very handly for plugins, you can use `meta.afOrder` property in the injection instantiation. The higher the number, the earlier the component will be rendered. For example
564+
565+
```ts title="/index.ts"
566+
{
567+
...
568+
customization: {
569+
globalInjections: {
570+
userMenu: [
571+
{
572+
file: '@@/CustomUserMenuItem.vue',
573+
meta: { afOrder: 10 }
574+
},
575+
{
576+
file: '@@/AnotherCustomUserMenuItem.vue',
577+
meta: { afOrder: 20 }
578+
},
579+
{
580+
file: '@@/LastCustomUserMenuItem.vue',
581+
meta: { afOrder: 5 }
582+
},
583+
]
584+
}
585+
}
586+
...
587+
}
588+
```
589+
590+
## Order of components inserted by plugins
591+
592+
For plugins, the plugin developers encouraged to use `meta.afOrder` to control the order of injections and allow to pass it from plugin options.
593+
594+
For example "OAuth2 plugin", when registers a login button component for login page injection, uses `meta.afOrder` and sets it equal to 'YYY' passed in plugin options:
595+
596+
```ts title="/index.ts"
597+
// plugin CODE
598+
YYY.push({
599+
file: '@@/..vue',
600+
meta: {
601+
afOrder: this.pluginOptions.YYY || 0
602+
}
603+
})
604+
```
605+
606+
So you can jsut pass `YYY` option to the plugin to control the order of the injection.
607+
559608
## Custom scripts in head
560609
561610
If you want to inject tags in your html head:

adminforth/modules/styles.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,13 @@ export const styles = () => ({
356356
lightUserMenuSettingsButtonDropdownItemText: "alias:lightBreadcrumbsHomepageText",
357357
lightUserMenuSettingsButtonDropdownItemTextHover: "alias:lightBreadcrumbsHomepageTextHover",
358358

359-
359+
lightUserMenuBackground: "#FFFFFF",
360+
lightUserMenuBorder: "#f3f4f6",
361+
lightUserMenuText: "#111827",
362+
lightUserMenuItemBackground: "alias:lightUserMenuBackground",
363+
lightUserMenuItemBackgroundHover: "alias:lightUserMenuBackground",
364+
lightUserMenuItemText: "#000000",
365+
lightUserMenuItemTextHover: "#000000",
360366

361367
// colors for dark theme
362368
darkHtml: "#111827",
@@ -711,6 +717,14 @@ export const styles = () => ({
711717
darkUserMenuSettingsButtonDropdownItemText: "#FFFFFF",
712718
darkUserMenuSettingsButtonDropdownItemTextHover: "#FFFFFF",
713719

720+
darkUserMenuBackground: "alias:darkSidebar",
721+
darkUserMenuBorder: "alias:darkSidebarDevider",
722+
darkUserMenuText: "#FFFFFF",
723+
darkUserMenuItemBackground: "alias:darkSidebar",
724+
darkUserMenuItemBackgroundHover: "alias:darkSidebarItemHover",
725+
darkUserMenuItemText: "#FFFFFF",
726+
darkUserMenuItemTextHover: "#FFFFFF",
727+
714728
},
715729
boxShadow: {
716730
customLight: "0 4px 8px rgba(0, 0, 0, 0.1)", // Lighter shadow

adminforth/modules/utils.ts

Lines changed: 33 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import fs from 'fs';
44
import Fuse from 'fuse.js';
55
import crypto from 'crypto';
66
import AdminForth, { AdminForthConfig } from '../index.js';
7+
import { RateLimiterMemory, RateLimiterAbstract } from "rate-limiter-flexible";
78
// @ts-ignore-next-line
89

910

@@ -381,90 +382,50 @@ export function md5hash(str:string) {
381382
}
382383

383384
export class RateLimiter {
384-
static counterData = {};
385-
386-
/**
387-
* Very dirty version of ratelimiter for demo purposes (should not be considered as production ready)
388-
* Will be used as RateLimiter.checkRateLimit('key', '5/24h', clientIp)
389-
* Stores counter in this class, in RAM, resets limits on app restart.
390-
* Also it creates setTimeout for every call, so is not optimal for high load.
391-
* @param key - key to store rate limit for
392-
* @param limit - limit in format '5/24h' - 5 requests per 24 hours
393-
* @param clientIp
394-
*/
395-
static checkRateLimit(key: string, limit: string, clientIp: string) {
396-
397-
if (!limit) {
398-
throw new Error('Rate limit is not set');
399-
}
385+
// constructor, accepts string like 10/10m, or 20/10s, or 30/1d
400386

401-
if (!key) {
402-
throw new Error('Rate limit key is not set');
403-
}
404387

405-
if (!clientIp) {
406-
throw new Error('Client IP is not set');
407-
}
388+
rateLimiter: RateLimiterAbstract;
408389

409-
if (!limit.includes('/')) {
410-
throw new Error('Rate limit should be in format count/period, like 5/24h');
411-
}
412390

413-
// parse limit
414-
const [count, period] = limit.split('/');
415-
const [preiodAmount, periodType] = /(\d+)(\w+)/.exec(period).slice(1);
416-
const preiodAmountNumber = parseInt(preiodAmount);
417-
418-
// get current time
419-
const whenClear = new Date();
420-
if (periodType === 'h') {
421-
whenClear.setHours(whenClear.getHours() + preiodAmountNumber);
422-
} else if (periodType === 'd') {
423-
whenClear.setDate(whenClear.getDate() + preiodAmountNumber);
424-
} else if (periodType === 'm') {
425-
whenClear.setMinutes(whenClear.getMinutes() + preiodAmountNumber);
426-
} else if (periodType === 'y') {
427-
whenClear.setFullYear(whenClear.getFullYear() + preiodAmountNumber);
428-
} else if (periodType === 's') {
429-
whenClear.setSeconds(whenClear.getSeconds() + preiodAmountNumber);
430-
} else {
431-
throw new Error(`Unsupported period type for rate limiting: ${periodType}`);
391+
durStringToSeconds(rate: string): number {
392+
if (!rate) {
393+
throw new Error('Rate duration is required');
432394
}
433395

434-
435-
// get current counter
436-
const counter = this.counterData[key] && this.counterData[key][clientIp] || 0;
437-
if (counter >= count) {
438-
return { error: true };
396+
397+
const period = rate.slice(-1);
398+
const duration = parseInt(rate.slice(0, -1));
399+
if (period === 's') {
400+
return duration;
401+
} else if (period === 'm') {
402+
return duration * 60;
403+
} else if (period === 'h') {
404+
return duration * 60 * 60;
405+
} else if (period === 'd') {
406+
return duration * 60 * 60 * 24;
439407
}
440-
RateLimiter.incrementCounter(key, clientIp);
441-
setTimeout(() => {
442-
RateLimiter.decrementCounter(key, clientIp);
443-
}, whenClear.getTime() - Date.now());
408+
throw new Error(`Invalid rate duration period: ${period}`);
409+
}
444410

445-
return { error: false };
446411

412+
constructor(rate: string) {
413+
const [points, duration] = rate.split('/');
414+
const durationSeconds = this.durStringToSeconds(duration);
415+
const opts = {
416+
points: parseInt(points),
417+
duration: durationSeconds, // Per second
418+
};
419+
this.rateLimiter = new RateLimiterMemory(opts);
447420
}
448421

449-
static incrementCounter(key: string, ip: string) {
450-
if (!RateLimiter.counterData[key]) {
451-
RateLimiter.counterData[key] = {};
452-
}
453-
if (!RateLimiter.counterData[key][ip]) {
454-
RateLimiter.counterData[key][ip] = 0;
455-
}
456-
RateLimiter.counterData[key][ip]++;
457-
}
458422

459-
static decrementCounter(key: string, ip: string) {
460-
if (!RateLimiter.counterData[key]) {
461-
RateLimiter.counterData[key] = {};
462-
}
463-
if (!RateLimiter.counterData[key][ip]) {
464-
RateLimiter.counterData[key][ip] = 0;
465-
}
466-
if (RateLimiter.counterData[key][ip] > 0) {
467-
RateLimiter.counterData[key][ip]--;
423+
async consume(key: string) {
424+
try {
425+
await this.rateLimiter.consume(key);
426+
return true;
427+
} catch (rejRes) {
428+
return false;
468429
}
469430
}
470431

0 commit comments

Comments
 (0)