Compare commits

..

2 Commits

Author SHA1 Message Date
Ahmed Bouhuolia
8b0feb9022 fix(webapp): handle the too many requests error 2023-07-16 21:19:16 +02:00
Ahmed Bouhuolia
92f929152f feat(server): expose the api rate limit to the env vars 2023-07-16 21:15:13 +02:00
15 changed files with 111 additions and 98 deletions

View File

@@ -47,3 +47,6 @@ AGENDASH_AUTH_PASSWORD=123123
SIGNUP_DISABLED=false SIGNUP_DISABLED=false
SIGNUP_ALLOWED_DOMAINS= SIGNUP_ALLOWED_DOMAINS=
SIGNUP_ALLOWED_EMAILS= SIGNUP_ALLOWED_EMAILS=
# API rate limit (points,duration,block duration).
API_RATE_LIMIT=120,60,600

View File

@@ -1,9 +1,12 @@
import dotenv from 'dotenv'; import dotenv from 'dotenv';
import path from 'path'; import path from 'path';
import { toInteger } from 'lodash';
import { castCommaListEnvVarToArray, parseBoolean } from '@/utils'; import { castCommaListEnvVarToArray, parseBoolean } from '@/utils';
dotenv.config(); dotenv.config();
const API_RATE_LIMIT = process.env.API_RATE_LIMIT?.split(',') || [];
module.exports = { module.exports = {
/** /**
* Your favorite port * Your favorite port
@@ -130,9 +133,9 @@ module.exports = {
blockDuration: 60 * 15, blockDuration: 60 * 15,
}, },
requests: { requests: {
points: 60, points: API_RATE_LIMIT[0] ? toInteger(API_RATE_LIMIT[0]) : 120,
duration: 60, duration: API_RATE_LIMIT[1] ? toInteger(API_RATE_LIMIT[1]) : 60,
blockDuration: 60 * 10, blockDuration: API_RATE_LIMIT[2] ? toInteger(API_RATE_LIMIT[2]) : 60 * 10,
}, },
}, },

View File

@@ -21,21 +21,19 @@ export function BalanceSheetAlerts() {
refetchBalanceSheet(); refetchBalanceSheet();
}; };
// Can't display any error if the report is loading. // Can't display any error if the report is loading.
if (isLoading) { if (isLoading) return null;
return null;
}
// Can't continue if the cost compute job is not running.
if (!balanceSheet.meta.is_cost_compute_running) {
return null;
}
return ( return (
<FinancialComputeAlert> <If condition={balanceSheet.meta.is_cost_compute_running}>
<Icon icon="info-block" iconSize={12} />{' '} <FinancialComputeAlert>
<T id={'just_a_moment_we_re_calculating_your_cost_transactions'} /> <Icon icon="info-block" iconSize={12} />{' '}
<Button onClick={handleRecalcReport} minimal={true} small={true}> <T id={'just_a_moment_we_re_calculating_your_cost_transactions'} />
<T id={'report.compute_running.refresh'} />
</Button> <Button onClick={handleRecalcReport} minimal={true} small={true}>
</FinancialComputeAlert> <T id={'report.compute_running.refresh'} />
</Button>
</FinancialComputeAlert>
</If>
); );
} }

View File

@@ -7,7 +7,6 @@ import FinancialLoadingBar from '../FinancialLoadingBar';
import { dynamicColumns } from './dynamicColumns'; import { dynamicColumns } from './dynamicColumns';
import { useCashFlowStatementContext } from './CashFlowStatementProvider'; import { useCashFlowStatementContext } from './CashFlowStatementProvider';
import { FinancialComputeAlert } from '../FinancialReportPage';
/** /**
* Retrieve cash flow statement columns. * Retrieve cash flow statement columns.
@@ -28,7 +27,6 @@ export const useCashFlowStatementColumns = () => {
*/ */
export function CashFlowStatementLoadingBar() { export function CashFlowStatementLoadingBar() {
const { isCashFlowFetching } = useCashFlowStatementContext(); const { isCashFlowFetching } = useCashFlowStatementContext();
return ( return (
<If condition={isCashFlowFetching}> <If condition={isCashFlowFetching}>
<FinancialLoadingBar /> <FinancialLoadingBar />
@@ -47,21 +45,20 @@ export function CashFlowStatementAlerts() {
const handleRecalcReport = () => { const handleRecalcReport = () => {
refetchCashFlow(); refetchCashFlow();
}; };
// Can't display any error if the report is loading // Can't display any error if the report is loading
if (isCashFlowLoading) { if (isCashFlowLoading) {
return null; return null;
} }
// Can't continue if the cost compute is not running.
if (!cashFlowStatement.meta.is_cost_compute_running) {
return null;
}
return ( return (
<FinancialComputeAlert> <If condition={cashFlowStatement.meta.is_cost_compute_running}>
<Icon icon="info-block" iconSize={12} /> <div className="alert-compute-running">
<T id={'just_a_moment_we_re_calculating_your_cost_transactions'} /> <Icon icon="info-block" iconSize={12} />
<Button onClick={handleRecalcReport} minimal={true} small={true}> <T id={'just_a_moment_we_re_calculating_your_cost_transactions'} />
<T id={'refresh'} /> <Button onClick={handleRecalcReport} minimal={true} small={true}>
</Button> <T id={'refresh'} />
</FinancialComputeAlert> </Button>
</div>
</If>
); );
} }

View File

@@ -8,7 +8,6 @@ import { getColumnWidth } from '@/utils';
import { useGeneralLedgerContext } from './GeneralLedgerProvider'; import { useGeneralLedgerContext } from './GeneralLedgerProvider';
import FinancialLoadingBar from '../FinancialLoadingBar'; import FinancialLoadingBar from '../FinancialLoadingBar';
import { FinancialComputeAlert } from '../FinancialReportPage';
import { Align } from '@/constants'; import { Align } from '@/constants';
/** /**
@@ -117,18 +116,17 @@ export function GeneralLedgerSheetAlerts() {
if (isLoading) { if (isLoading) {
return null; return null;
} }
// Can't continue if the cost compute job is not running.
if (!generalLedger.meta.is_cost_compute_running) {
return null;
}
return ( return (
<FinancialComputeAlert> <If condition={generalLedger.meta.is_cost_compute_running}>
<Icon icon="info-block" iconSize={12} /> <div class="alert-compute-running">
<T id={'just_a_moment_we_re_calculating_your_cost_transactions'} /> <Icon icon="info-block" iconSize={12} />
<Button onClick={handleRecalcReport} minimal={true} small={true}> <T id={'just_a_moment_we_re_calculating_your_cost_transactions'} />
<T id={'refresh'} /> <Button onClick={handleRecalcReport} minimal={true} small={true}>
</Button> <T id={'refresh'} />
</FinancialComputeAlert> </Button>
</div>
</If>
); );
} }

View File

@@ -6,7 +6,6 @@ import { Icon, If, FormattedMessage as T } from '@/components';
import { dynamicColumns } from './utils'; import { dynamicColumns } from './utils';
import FinancialLoadingBar from '../FinancialLoadingBar'; import FinancialLoadingBar from '../FinancialLoadingBar';
import { useInventoryItemDetailsContext } from './InventoryItemDetailsProvider'; import { useInventoryItemDetailsContext } from './InventoryItemDetailsProvider';
import { FinancialComputeAlert } from '../FinancialReportPage';
/** /**
* Retrieve inventory item details columns. * Retrieve inventory item details columns.
@@ -54,19 +53,17 @@ export function InventoryItemDetailsAlerts() {
if (isInventoryItemDetailsLoading) { if (isInventoryItemDetailsLoading) {
return null; return null;
} }
// Can't continue if the cost compute job is running.
if (!inventoryItemDetails.meta.is_cost_compute_running) {
return null;
}
return ( return (
<FinancialComputeAlert> <If condition={inventoryItemDetails.meta.is_cost_compute_running}>
<Icon icon="info-block" iconSize={12} /> <div className="alert-compute-running">
<T id={'just_a_moment_we_re_calculating_your_cost_transactions'} /> <Icon icon="info-block" iconSize={12} />
<T id={'just_a_moment_we_re_calculating_your_cost_transactions'} />
<Button onClick={handleRecalcReport} minimal={true} small={true}> <Button onClick={handleRecalcReport} minimal={true} small={true}>
<T id={'refresh'} /> <T id={'refresh'} />
</Button> </Button>
</FinancialComputeAlert> </div>
</If>
); );
} }

View File

@@ -7,7 +7,6 @@ import { Button } from '@blueprintjs/core';
import { Icon, If, FormattedMessage as T } from '@/components'; import { Icon, If, FormattedMessage as T } from '@/components';
import { useJournalSheetContext } from './JournalProvider'; import { useJournalSheetContext } from './JournalProvider';
import FinancialLoadingBar from '../FinancialLoadingBar'; import FinancialLoadingBar from '../FinancialLoadingBar';
import { FinancialComputeAlert } from '../FinancialReportPage';
import { Align } from '@/constants'; import { Align } from '@/constants';
@@ -100,18 +99,17 @@ export function JournalSheetAlerts() {
if (isLoading) { if (isLoading) {
return null; return null;
} }
// Can't continue if the cost compute job is running.
if (!journalSheet.meta.is_cost_compute_running) {
return null;
}
return (
<FinancialComputeAlert>
<Icon icon="info-block" iconSize={12} />
<T id={'just_a_moment_we_re_calculating_your_cost_transactions'} />
<Button onClick={handleRecalcReport} minimal={true} small={true}> return (
<T id={'refresh'} /> <If condition={journalSheet.meta.is_cost_compute_running}>
</Button> <div class="alert-compute-running">
</FinancialComputeAlert> <Icon icon="info-block" iconSize={12} />
<T id={'just_a_moment_we_re_calculating_your_cost_transactions'} />
<Button onClick={handleRecalcReport} minimal={true} small={true}>
<T id={'refresh'} />
</Button>
</div>
</If>
); );
} }

View File

@@ -13,7 +13,7 @@ import withProfitLossActions from './withProfitLossActions';
import { useProfitLossSheetQuery } from './utils'; import { useProfitLossSheetQuery } from './utils';
import { ProfitLossSheetProvider } from './ProfitLossProvider'; import { ProfitLossSheetProvider } from './ProfitLossProvider';
import { ProfitLossSheetAlerts, ProfitLossSheetLoadingBar } from './components'; import { ProfitLossSheetLoadingBar } from './components';
import { ProfitLossBody } from './ProfitLossBody'; import { ProfitLossBody } from './ProfitLossBody';
/** /**
@@ -58,7 +58,7 @@ function ProfitLossSheet({
onNumberFormatSubmit={handleNumberFormatSubmit} onNumberFormatSubmit={handleNumberFormatSubmit}
/> />
<ProfitLossSheetLoadingBar /> <ProfitLossSheetLoadingBar />
<ProfitLossSheetAlerts /> {/* <ProfitLossSheetAlerts /> */}
<DashboardPageContent> <DashboardPageContent>
<ProfitLossSheetHeader <ProfitLossSheetHeader

View File

@@ -4,7 +4,6 @@ import { Button } from '@blueprintjs/core';
import { Icon, If, FormattedMessage as T } from '@/components'; import { Icon, If, FormattedMessage as T } from '@/components';
import { useProfitLossSheetContext } from './ProfitLossProvider'; import { useProfitLossSheetContext } from './ProfitLossProvider';
import { FinancialComputeAlert } from '../FinancialReportPage';
import FinancialLoadingBar from '../FinancialLoadingBar'; import FinancialLoadingBar from '../FinancialLoadingBar';
/** /**
@@ -35,18 +34,17 @@ export function ProfitLossSheetAlerts() {
if (isLoading) { if (isLoading) {
return null; return null;
} }
// Can't continue if the cost compute job is not running.
if (!profitLossSheet.meta.is_cost_compute_running) {
return null;
}
return (
<FinancialComputeAlert>
<Icon icon="info-block" iconSize={12} />
<T id={'just_a_moment_we_re_calculating_your_cost_transactions'} />
<Button onClick={handleRecalcReport} minimal={true} small={true}> return (
<T id={'refresh'} /> <If condition={profitLossSheet.meta.is_cost_compute_running}>
</Button> <div class="alert-compute-running">
</FinancialComputeAlert> <Icon icon="info-block" iconSize={12} />
<T id={'just_a_moment_we_re_calculating_your_cost_transactions'} />
<Button onClick={handleRecalcReport} minimal={true} small={true}>
<T id={'refresh'} />
</Button>
</div>
</If>
); );
} }

View File

@@ -8,9 +8,9 @@ import { getColumnWidth } from '@/utils';
import { CellTextSpan } from '@/components/Datatable/Cells'; import { CellTextSpan } from '@/components/Datatable/Cells';
import { If, Icon, FormattedMessage as T } from '@/components'; import { If, Icon, FormattedMessage as T } from '@/components';
import { useTrialBalanceSheetContext } from './TrialBalanceProvider'; import { useTrialBalanceSheetContext } from './TrialBalanceProvider';
import { FinancialComputeAlert } from '../FinancialReportPage';
import FinancialLoadingBar from '../FinancialLoadingBar'; import FinancialLoadingBar from '../FinancialLoadingBar';
/** /**
* Retrieves the credit column. * Retrieves the credit column.
*/ */
@@ -114,19 +114,17 @@ export function TrialBalanceSheetAlerts() {
if (isLoading) { if (isLoading) {
return null; return null;
} }
// Can't continue if the cost compute job is not running.
if (!meta.is_cost_compute_running) {
return null;
}
return ( return (
<FinancialComputeAlert> <If condition={meta.is_cost_compute_running}>
<Icon icon="info-block" iconSize={12} /> <div class="alert-compute-running">
<T id={'just_a_moment_we_re_calculating_your_cost_transactions'} /> <Icon icon="info-block" iconSize={12} />
<T id={'just_a_moment_we_re_calculating_your_cost_transactions'} />
<Button onClick={handleRecalcReport} minimal={true} small={true}> <Button onClick={handleRecalcReport} minimal={true} small={true}>
<T id={'refresh'} /> <T id={'refresh'} />
</Button> </Button>
</FinancialComputeAlert> </div>
</If>
); );
} }

View File

@@ -9,6 +9,7 @@ import { compose } from '@/utils';
let toastKeySessionExpired; let toastKeySessionExpired;
let toastKeySomethingWrong; let toastKeySomethingWrong;
let toastTooManyRequests;
function GlobalErrors({ function GlobalErrors({
// #withGlobalErrors // #withGlobalErrors
@@ -41,6 +42,18 @@ function GlobalErrors({
toastKeySomethingWrong, toastKeySomethingWrong,
); );
} }
if (globalErrors.too_many_requests) {
toastTooManyRequests = AppToaster.show(
{
message: intl.get('global_error.too_many_requests'),
intent: Intent.DANGER,
onDismiss: () => {
globalErrorsSet({ too_many_requests: false });
},
},
toastTooManyRequests,
);
}
if (globalErrors.access_denied) { if (globalErrors.access_denied) {
toastKeySomethingWrong = AppToaster.show( toastKeySomethingWrong = AppToaster.show(
{ {

View File

@@ -60,6 +60,9 @@ export default function useApiRequest() {
if (status === 403) { if (status === 403) {
setGlobalErrors({ access_denied: true }); setGlobalErrors({ access_denied: true });
} }
if (status === 429) {
setGlobalErrors({ too_many_requests: true });
}
if (status === 400) { if (status === 400) {
const lockedError = data.errors.find( const lockedError = data.errors.find(
(error) => error.type === 'TRANSACTIONS_DATE_LOCKED', (error) => error.type === 'TRANSACTIONS_DATE_LOCKED',

View File

@@ -2292,5 +2292,6 @@
"sidebar.projects": "Projects", "sidebar.projects": "Projects",
"sidebar.new_project": "New Project", "sidebar.new_project": "New Project",
"sidebar.new_time_entry": "New Time Entry", "sidebar.new_time_entry": "New Time Entry",
"sidebar.project_profitability_summary": "Project Profitability Summary" "sidebar.project_profitability_summary": "Project Profitability Summary",
"global_error.too_many_requests": "Too many requests"
} }

View File

@@ -1,10 +1,12 @@
@import '@/style/variables.scss';
.bigcapital-loading { .bigcapital-loading {
height: 100%; height: 100%;
width: 100%; width: 100%;
position: fixed; position: fixed;
display: flex; display: flex;
background: #fff; background: #fff;
z-index: 999999; z-index: $zindex-dashboard-splash-screen;
.center { .center {
width: auto; width: auto;

View File

@@ -45,3 +45,7 @@ $form-check-input-checked-color: #fff;
$form-check-input-checked-bg-color: $blue1; $form-check-input-checked-bg-color: $blue1;
$form-check-input-checked-bg-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' viewBox='0 0 16 16' enable-background='new 0 0 16 16' xml:space='preserve'><g id='small_tick_1_'><g><path fill='#{$form-check-input-checked-color}' fill-rule='evenodd' clip-rule='evenodd' d='M12,5c-0.28,0-0.53,0.11-0.71,0.29L7,9.59L4.71,7.29C4.53,7.11,4.28,7,4,7C3.45,7,3,7.45,3,8c0,0.28,0.11,0.53,0.29,0.71l3,3C6.47,11.89,6.72,12,7,12s0.53-0.11,0.71-0.29l5-5C12.89,6.53,13,6.28,13,6C13,5.45,12.55,5,12,5z'/></g></g></svg>") !default; $form-check-input-checked-bg-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' viewBox='0 0 16 16' enable-background='new 0 0 16 16' xml:space='preserve'><g id='small_tick_1_'><g><path fill='#{$form-check-input-checked-color}' fill-rule='evenodd' clip-rule='evenodd' d='M12,5c-0.28,0-0.53,0.11-0.71,0.29L7,9.59L4.71,7.29C4.53,7.11,4.28,7,4,7C3.45,7,3,7.45,3,8c0,0.28,0.11,0.53,0.29,0.71l3,3C6.47,11.89,6.72,12,7,12s0.53-0.11,0.71-0.29l5-5C12.89,6.53,13,6.28,13,6C13,5.45,12.55,5,12,5z'/></g></g></svg>") !default;
$form-check-input-indeterminate-bg-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' viewBox='0 0 16 16' enable-background='new 0 0 16 16' xml:space='preserve'><g id='small_tick_1_'><g><path fill='#{$form-check-input-checked-color}' fill-rule='evenodd' clip-rule='evenodd' d='M11,7H5C4.45,7,4,7.45,4,8c0,0.55,0.45,1,1,1h6c0.55,0,1-0.45,1-1C12,7.45,11.55,7,11,7z'/></g></g></svg>") !default; $form-check-input-indeterminate-bg-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' viewBox='0 0 16 16' enable-background='new 0 0 16 16' xml:space='preserve'><g id='small_tick_1_'><g><path fill='#{$form-check-input-checked-color}' fill-rule='evenodd' clip-rule='evenodd' d='M11,7H5C4.45,7,4,7.45,4,8c0,0.55,0.45,1,1,1h6c0.55,0,1-0.45,1-1C12,7.45,11.55,7,11,7z'/></g></g></svg>") !default;
// z-indexs
$zindex-dashboard-splash-screen: 39;
$zindex-toast: 40;