feat: Enhance server security and performance by adding helmet for security headers, implementing rate limiting, and centralizing error handling; update frontend API calls to use a dedicated api module for consistency
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 4m37s
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 4m37s
This commit is contained in:
+31
-3
@@ -8,11 +8,27 @@ const compression = require('compression');
|
||||
const Ajv = require('ajv');
|
||||
const net = require('net');
|
||||
const crypto = require('crypto');
|
||||
const helmet = require('helmet');
|
||||
const rateLimit = require('express-rate-limit');
|
||||
|
||||
const app = express();
|
||||
const port = 3001;
|
||||
const port = Number(process.env.PORT) || 3001;
|
||||
|
||||
// CORS: при необходимости сузьте до списка доменов (пример ниже)
|
||||
// const allowed = (process.env.CORS_ORIGINS || '').split(',').map(s => s.trim()).filter(Boolean);
|
||||
// app.use(cors({ origin: (origin, cb) => { if (!origin || allowed.length===0 || allowed.includes(origin)) return cb(null, true); cb(new Error('CORS blocked')); }, credentials: true }));
|
||||
app.use(cors());
|
||||
app.use(helmet({
|
||||
crossOriginResourcePolicy: { policy: 'cross-origin' },
|
||||
}));
|
||||
app.disable('x-powered-by');
|
||||
const limiter = rateLimit({
|
||||
windowMs: 15 * 60 * 1000,
|
||||
max: 1000,
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
});
|
||||
app.use(limiter);
|
||||
app.use(express.json());
|
||||
app.use(compression());
|
||||
// Disable Express auto-ETag to avoid weak ETags on JSON bodies
|
||||
@@ -24,6 +40,8 @@ app.use((req, res, next) => {
|
||||
next();
|
||||
});
|
||||
|
||||
// Централизованный обработчик ошибок (должен быть подключён ПОСЛЕ роутов — см. ниже второе use)
|
||||
|
||||
// Helpers: meta and responses
|
||||
function toIso(x) {
|
||||
try { return new Date(x).toISOString(); } catch { return null; }
|
||||
@@ -1739,6 +1757,16 @@ app.get('*', (req, res) => {
|
||||
res.sendFile(path.join(__dirname, 'public', 'index.html'));
|
||||
});
|
||||
|
||||
// Централизованный error handler (последний middleware)
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
app.use((err, req, res, next) => {
|
||||
const status = typeof err?.status === 'number' ? err.status : 500;
|
||||
const code = err?.code || 'E_INTERNAL';
|
||||
const message = status === 500 && process.env.NODE_ENV === 'production' ? 'Internal Server Error' : (err?.message || 'Error');
|
||||
try { console.error('error:', err); } catch {}
|
||||
res.status(status).json({ message, code });
|
||||
});
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`Server is running on http://localhost:${port}`);
|
||||
});
|
||||
console.log(`Server is running on http://localhost:${port}`);
|
||||
});
|
||||
Generated
+3
-297
@@ -12,9 +12,7 @@
|
||||
"@tabler/icons-react": "^3.34.0",
|
||||
"@xyflow/react": "^12.3.4",
|
||||
"axios": "^1.10.0",
|
||||
"bootstrap": "^5.3.7",
|
||||
"react": "^19.1.0",
|
||||
"react-bootstrap": "^2.10.10",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-router-dom": "^6.30.1",
|
||||
"react-sigma": "^1.2.35"
|
||||
@@ -248,15 +246,6 @@
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.27.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz",
|
||||
"integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/template": {
|
||||
"version": "7.27.2",
|
||||
"dev": true,
|
||||
@@ -554,21 +543,6 @@
|
||||
"url": "https://opencollective.com/popperjs"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-aria/ssr": {
|
||||
"version": "3.9.9",
|
||||
"resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.9.tgz",
|
||||
"integrity": "sha512-2P5thfjfPy/np18e5wD4WPt8ydNXhij1jwA8oehxZTFqlgVMGXzcWKxTb4RtJrLFsqPO7RUQTiY8QJk0M4Vy2g==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@swc/helpers": "^0.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@remix-run/router": {
|
||||
"version": "1.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz",
|
||||
@@ -578,60 +552,6 @@
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@restart/hooks": {
|
||||
"version": "0.4.16",
|
||||
"resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz",
|
||||
"integrity": "sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dequal": "^2.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@restart/ui": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/@restart/ui/-/ui-1.9.4.tgz",
|
||||
"integrity": "sha512-N4C7haUc3vn4LTwVUPlkJN8Ach/+yIMvRuTVIhjilNHqegY60SGLrzud6errOMNJwSnmYFnt1J0H/k8FE3A4KA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.26.0",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@react-aria/ssr": "^3.5.0",
|
||||
"@restart/hooks": "^0.5.0",
|
||||
"@types/warning": "^3.0.3",
|
||||
"dequal": "^2.0.3",
|
||||
"dom-helpers": "^5.2.0",
|
||||
"uncontrollable": "^8.0.4",
|
||||
"warning": "^4.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.14.0",
|
||||
"react-dom": ">=16.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@restart/ui/node_modules/@restart/hooks": {
|
||||
"version": "0.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.5.1.tgz",
|
||||
"integrity": "sha512-EMoH04NHS1pbn07iLTjIjgttuqb7qu4+/EyhAx27MHpoENcB2ZdSsLTNxmKD+WEPnZigo62Qc8zjGnNxoSE/5Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dequal": "^2.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@restart/ui/node_modules/uncontrollable": {
|
||||
"version": "8.0.4",
|
||||
"resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-8.0.4.tgz",
|
||||
"integrity": "sha512-ulRWYWHvscPFc0QQXvyJjY6LIXU56f0h8pQFvhxiKk5V1fcI8gp9Ht9leVAhrVjzqMw0BgjspBINx9r6oyJUvQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": ">=16.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/pluginutils": {
|
||||
"version": "1.0.0-beta.19",
|
||||
"dev": true,
|
||||
@@ -650,15 +570,6 @@
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@swc/helpers": {
|
||||
"version": "0.5.17",
|
||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz",
|
||||
"integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tabler/core": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@tabler/core/-/core-1.3.2.tgz",
|
||||
@@ -817,14 +728,9 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/prop-types": {
|
||||
"version": "15.7.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
|
||||
"integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "19.1.8",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"csstype": "^3.0.2"
|
||||
@@ -838,21 +744,6 @@
|
||||
"@types/react": "^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-transition-group": {
|
||||
"version": "4.4.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz",
|
||||
"integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/warning": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz",
|
||||
"integrity": "sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@vitejs/plugin-react": {
|
||||
"version": "4.6.0",
|
||||
"dev": true,
|
||||
@@ -975,25 +866,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/bootstrap": {
|
||||
"version": "5.3.7",
|
||||
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.7.tgz",
|
||||
"integrity": "sha512-7KgiD8UHjfcPBHEpDNg+zGz8L3LqR3GVwqZiBRFX04a1BCArZOz1r2kjly2HQ0WokqTO0v1nF+QAt8dsW4lKlw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/twbs"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/bootstrap"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@popperjs/core": "^2.11.8"
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.12",
|
||||
"dev": true,
|
||||
@@ -1093,12 +965,6 @@
|
||||
"integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/classnames": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
|
||||
"integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"dev": true,
|
||||
@@ -1150,6 +1016,7 @@
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.1.3",
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/d3-color": {
|
||||
@@ -1285,25 +1152,6 @@
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dequal": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
|
||||
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/dom-helpers": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
|
||||
"integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.8.7",
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"license": "MIT",
|
||||
@@ -1844,15 +1692,6 @@
|
||||
"node": ">=0.8.19"
|
||||
}
|
||||
},
|
||||
"node_modules/invariant": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
|
||||
"integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-extglob": {
|
||||
"version": "2.1.1",
|
||||
"dev": true,
|
||||
@@ -1879,6 +1718,7 @@
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
@@ -1968,18 +1808,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/loose-envify": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"loose-envify": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "5.1.1",
|
||||
"dev": true,
|
||||
@@ -2055,15 +1883,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/optionator": {
|
||||
"version": "0.9.4",
|
||||
"dev": true,
|
||||
@@ -2186,30 +2005,6 @@
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prop-types": {
|
||||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.4.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"react-is": "^16.13.1"
|
||||
}
|
||||
},
|
||||
"node_modules/prop-types-extra": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz",
|
||||
"integrity": "sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"react-is": "^16.3.2",
|
||||
"warning": "^4.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=0.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"license": "MIT"
|
||||
@@ -2229,37 +2024,6 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-bootstrap": {
|
||||
"version": "2.10.10",
|
||||
"resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.10.10.tgz",
|
||||
"integrity": "sha512-gMckKUqn8aK/vCnfwoBpBVFUGT9SVQxwsYrp9yDHt0arXMamxALerliKBxr1TPbntirK/HGrUAHYbAeQTa9GHQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.24.7",
|
||||
"@restart/hooks": "^0.4.9",
|
||||
"@restart/ui": "^1.9.4",
|
||||
"@types/prop-types": "^15.7.12",
|
||||
"@types/react-transition-group": "^4.4.6",
|
||||
"classnames": "^2.3.2",
|
||||
"dom-helpers": "^5.2.1",
|
||||
"invariant": "^2.2.4",
|
||||
"prop-types": "^15.8.1",
|
||||
"prop-types-extra": "^1.1.0",
|
||||
"react-transition-group": "^4.4.5",
|
||||
"uncontrollable": "^7.2.1",
|
||||
"warning": "^4.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": ">=16.14.8",
|
||||
"react": ">=16.14.0",
|
||||
"react-dom": ">=16.14.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "19.1.0",
|
||||
"license": "MIT",
|
||||
@@ -2270,18 +2034,6 @@
|
||||
"react": "^19.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-lifecycles-compat": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
|
||||
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-refresh": {
|
||||
"version": "0.17.0",
|
||||
"dev": true,
|
||||
@@ -2335,22 +2087,6 @@
|
||||
"react-dom": ">=15.3"
|
||||
}
|
||||
},
|
||||
"node_modules/react-transition-group": {
|
||||
"version": "4.4.5",
|
||||
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
|
||||
"integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"dom-helpers": "^5.0.1",
|
||||
"loose-envify": "^1.4.0",
|
||||
"prop-types": "^15.6.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.6.0",
|
||||
"react-dom": ">=16.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve-from": {
|
||||
"version": "4.0.0",
|
||||
"dev": true,
|
||||
@@ -2435,12 +2171,6 @@
|
||||
"url": "https://github.com/sponsors/SuperchupuDev"
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/type-check": {
|
||||
"version": "0.4.0",
|
||||
"dev": true,
|
||||
@@ -2452,21 +2182,6 @@
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/uncontrollable": {
|
||||
"version": "7.2.1",
|
||||
"resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz",
|
||||
"integrity": "sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.6.3",
|
||||
"@types/react": ">=16.9.11",
|
||||
"invariant": "^2.2.4",
|
||||
"react-lifecycles-compat": "^3.0.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=15.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/update-browserslist-db": {
|
||||
"version": "1.1.3",
|
||||
"dev": true,
|
||||
@@ -2899,15 +2614,6 @@
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/warning": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
|
||||
"integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"dev": true,
|
||||
|
||||
@@ -13,9 +13,7 @@
|
||||
"@tabler/core": "^1.3.2",
|
||||
"@tabler/icons-react": "^3.34.0",
|
||||
"axios": "^1.10.0",
|
||||
"bootstrap": "^5.3.7",
|
||||
"react": "^19.1.0",
|
||||
"react-bootstrap": "^2.10.10",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-router-dom": "^6.30.1",
|
||||
"react-sigma": "^1.2.35",
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useState, useEffect, createContext, useContext } from 'react';
|
||||
import { Container } from 'react-bootstrap';
|
||||
import {
|
||||
IconBrandTabler,
|
||||
IconWorld,
|
||||
@@ -23,7 +22,7 @@ import CommunitiesManager from './CommunitiesManager';
|
||||
import Dashboard from './Dashboard';
|
||||
import './App.css';
|
||||
import { NotifyProvider } from './components/NotifyProvider.jsx';
|
||||
import axios from 'axios';
|
||||
// axios не используется напрямую; сетевые вызовы через src/lib/api.js
|
||||
|
||||
// --- Simple i18n (RU/EN) ---
|
||||
const LanguageContext = createContext({ lang: 'ru', setLang: () => {}, t: (k) => k });
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import axios from 'axios';
|
||||
import api from './lib/api.js';
|
||||
import {
|
||||
IconPlus,
|
||||
IconTrash,
|
||||
@@ -30,7 +30,7 @@ function AutoUrlManager() {
|
||||
const fetchUrls = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await axios.get('/api/auto-urls');
|
||||
const response = await api.get('/auto-urls');
|
||||
setUrls(response.data);
|
||||
} catch (error) {
|
||||
console.error('Error fetching URLs:', error);
|
||||
@@ -91,7 +91,7 @@ function AutoUrlManager() {
|
||||
return;
|
||||
}
|
||||
|
||||
await axios.post('/api/auto-urls', { urls: validUrls });
|
||||
await api.post('/auto-urls', { urls: validUrls });
|
||||
setUrls(validUrls);
|
||||
setMessage('URL-адреса сохранены успешно');
|
||||
setMessageType('success');
|
||||
@@ -109,7 +109,7 @@ function AutoUrlManager() {
|
||||
setProcessing(true);
|
||||
setMessage('');
|
||||
|
||||
const response = await axios.post('/api/auto-urls/process');
|
||||
const response = await api.post('/auto-urls/process');
|
||||
setMessage(response.data.message);
|
||||
setMessageType('success');
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import axios from 'axios';
|
||||
import api from './lib/api.js';
|
||||
import {
|
||||
IconPlus,
|
||||
IconEdit,
|
||||
@@ -114,7 +114,7 @@ function BillingManager() {
|
||||
const fetchBillingData = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await axios.get(`${API_URL}/billing`);
|
||||
const response = await api.get(`/billing`);
|
||||
const normalized = (response.data || []).map((item, idx) => ({
|
||||
id: item.id ?? String(idx + 1),
|
||||
payments: Array.isArray(item.payments)
|
||||
@@ -139,7 +139,7 @@ function BillingManager() {
|
||||
const handleSaveChanges = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
await axios.post(`${API_URL}/billing`, { domains: billingData });
|
||||
await api.post(`/billing`, { domains: billingData });
|
||||
setSuccess('Данные успешно сохранены');
|
||||
setTimeout(() => setSuccess(''), 3000);
|
||||
} catch (err) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import axios from 'axios';
|
||||
import api from './lib/api.js';
|
||||
import {
|
||||
IconPlus,
|
||||
IconSearch,
|
||||
@@ -39,7 +39,7 @@ function CommunitiesManager() {
|
||||
const fetchItems = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await axios.get(`${API_URL}/communities`);
|
||||
const res = await api.get(`/communities`);
|
||||
setItems(res.data);
|
||||
setError('');
|
||||
} catch (e) {
|
||||
@@ -53,7 +53,7 @@ function CommunitiesManager() {
|
||||
const saveAll = async (data) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
await axios.post(`${API_URL}/communities`, { communities: data });
|
||||
await api.post(`/communities`, { communities: data });
|
||||
setSuccess('Справочник сохранён!');
|
||||
setTimeout(() => setSuccess(''), 3000);
|
||||
} catch (e) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import axios from 'axios';
|
||||
import api from './lib/api.js';
|
||||
import {
|
||||
IconWorld,
|
||||
IconNetwork,
|
||||
@@ -80,12 +80,12 @@ function Dashboard() {
|
||||
try {
|
||||
// Загружаем данные с обработкой ошибок для каждого endpoint
|
||||
const results = await Promise.allSettled([
|
||||
axios.get('/api/domains-new', { params: { countOnly: true } }),
|
||||
axios.get('/api/ip-ranges', { params: { countOnly: true } }),
|
||||
axios.get('/api/asns', { params: { countOnly: true } }),
|
||||
axios.get('/api/servers'),
|
||||
axios.get('/api/servers/availability', { params: { ttlSeconds: 60 } }),
|
||||
axios.get('/api/s3/last-modified')
|
||||
api.get('/domains-new', { params: { countOnly: true } }),
|
||||
api.get('/ip-ranges', { params: { countOnly: true } }),
|
||||
api.get('/asns', { params: { countOnly: true } }),
|
||||
api.get('/servers'),
|
||||
api.get('/servers/availability', { params: { ttlSeconds: 60 } }),
|
||||
api.get('/s3/last-modified')
|
||||
]);
|
||||
|
||||
// Обрабатываем результаты
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import axios from 'axios';
|
||||
import api from './lib/api.js';
|
||||
import {
|
||||
IconPlus,
|
||||
IconSearch,
|
||||
@@ -44,7 +44,7 @@ function DataManager({ entityName, entityKey, placeholder }) {
|
||||
const fetchItems = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await axios.get(`${API_URL}/${entityKey}`);
|
||||
const response = await api.get(`/${entityKey}`);
|
||||
setItems(response.data);
|
||||
setError('');
|
||||
} catch (error) {
|
||||
@@ -102,7 +102,7 @@ function DataManager({ entityName, entityKey, placeholder }) {
|
||||
const handleSaveChanges = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
await axios.post(`${API_URL}/${entityKey}`, { domains: items });
|
||||
await api.post(`/${entityKey}`, { domains: items });
|
||||
setSuccess('Изменения успешно сохранены!');
|
||||
setTimeout(() => setSuccess(''), 3000);
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import axios from 'axios';
|
||||
import api from './lib/api.js';
|
||||
import {
|
||||
IconPlus,
|
||||
IconSearch,
|
||||
@@ -81,7 +81,7 @@ function FilterManager() {
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
const res = await axios.get(`${API_URL}/communities`);
|
||||
const res = await api.get(`/communities`);
|
||||
setCommunities(Array.isArray(res.data) ? res.data : []);
|
||||
} catch (e) {
|
||||
// мягко игнорируем, автокомплит необязателен
|
||||
@@ -108,7 +108,7 @@ function FilterManager() {
|
||||
const fetchServerFilters = async (serverId) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await axios.get(`${API_URL}/server-filters/${serverId}`);
|
||||
const response = await api.get(`/server-filters/${serverId}`);
|
||||
setServerFilters(response.data);
|
||||
setError('');
|
||||
} catch (error) {
|
||||
@@ -122,7 +122,7 @@ function FilterManager() {
|
||||
// Функции для управления серверами
|
||||
const fetchServers = async () => {
|
||||
try {
|
||||
const response = await axios.get(`${API_URL}/server-configs`);
|
||||
const response = await api.get(`/server-configs`);
|
||||
setServers(response.data);
|
||||
} catch (error) {
|
||||
console.error('Error fetching servers:', error);
|
||||
@@ -134,7 +134,7 @@ function FilterManager() {
|
||||
const fetchSimpleFilters = async () => {
|
||||
setSimpleLoading(true);
|
||||
try {
|
||||
const response = await axios.get(`${API_URL}/simple-filters`);
|
||||
const response = await api.get(`/simple-filters`);
|
||||
setSimpleFilters(response.data);
|
||||
} catch (error) {
|
||||
console.error('Error fetching simple filters:', error);
|
||||
@@ -158,7 +158,7 @@ function FilterManager() {
|
||||
};
|
||||
|
||||
const updatedServers = [...servers, serverToAdd];
|
||||
await axios.post(`${API_URL}/server-configs`, { servers: updatedServers });
|
||||
await api.post(`/server-configs`, { servers: updatedServers });
|
||||
|
||||
setServers(updatedServers);
|
||||
setNewServer({ name: '', description: '', enabled: true });
|
||||
@@ -177,7 +177,7 @@ function FilterManager() {
|
||||
await axios.post(`${API_URL}/server-configs`, { servers: updatedServers });
|
||||
|
||||
// Удаляем все файлы сервера (конфигурацию и фильтры)
|
||||
await axios.delete(`${API_URL}/server-configs/${serverId}/complete`);
|
||||
await api.delete(`/server-configs/${serverId}/complete`);
|
||||
|
||||
setServers(updatedServers);
|
||||
if (selectedServer?.id === serverId) {
|
||||
@@ -201,7 +201,7 @@ function FilterManager() {
|
||||
return;
|
||||
}
|
||||
|
||||
await axios.post(`${API_URL}/server-configs/${serverId}`, { config });
|
||||
await api.post(`/server-configs/${serverId}`, { config });
|
||||
|
||||
setSuccess(`Конфигурация для сервера "${servers.find(s => s.id === serverId)?.name}" сохранена!`);
|
||||
setTimeout(() => setSuccess(''), 3000);
|
||||
@@ -217,7 +217,7 @@ function FilterManager() {
|
||||
|
||||
const handleLoadServerConfig = async (serverId) => {
|
||||
try {
|
||||
const response = await axios.get(`${API_URL}/server-configs/${serverId}`);
|
||||
const response = await api.get(`/server-configs/${serverId}`);
|
||||
setGeneratedConfig(response.data.config);
|
||||
setPreviewModalOpen(true);
|
||||
} catch (error) {
|
||||
@@ -249,11 +249,11 @@ function FilterManager() {
|
||||
setLoading(true);
|
||||
try {
|
||||
// Получаем фильтры исходного сервера
|
||||
const sourceFiltersResponse = await axios.get(`${API_URL}/server-filters/${sourceServerId}`);
|
||||
const sourceFiltersResponse = await api.get(`/server-filters/${sourceServerId}`);
|
||||
const sourceFilters = sourceFiltersResponse.data;
|
||||
|
||||
// Получаем конфигурацию исходного сервера
|
||||
const sourceConfigResponse = await axios.get(`${API_URL}/server-configs/${sourceServerId}`);
|
||||
const sourceConfigResponse = await api.get(`/server-configs/${sourceServerId}`);
|
||||
const sourceConfig = sourceConfigResponse.data.config;
|
||||
|
||||
if (!sourceConfig || sourceConfig === '// Конфигурация не найдена') {
|
||||
@@ -267,10 +267,10 @@ function FilterManager() {
|
||||
for (const targetServer of targetServers) {
|
||||
try {
|
||||
// Копируем фильтры
|
||||
await axios.post(`${API_URL}/server-filters/${targetServer.id}`, { filters: sourceFilters });
|
||||
await api.post(`/server-filters/${targetServer.id}`, { filters: sourceFilters });
|
||||
|
||||
// Копируем конфигурацию
|
||||
await axios.post(`${API_URL}/server-configs/${targetServer.id}`, { config: sourceConfig });
|
||||
await api.post(`/server-configs/${targetServer.id}`, { config: sourceConfig });
|
||||
|
||||
results.push({ server: targetServer.name, status: 'success' });
|
||||
} catch (error) {
|
||||
@@ -349,10 +349,10 @@ function FilterManager() {
|
||||
for (const server of enabledServers) {
|
||||
try {
|
||||
// Копируем фильтры
|
||||
await axios.post(`${API_URL}/server-filters/${server.id}`, { filters: serverFilters });
|
||||
await api.post(`/server-filters/${server.id}`, { filters: serverFilters });
|
||||
|
||||
// Копируем конфигурацию
|
||||
await axios.post(`${API_URL}/server-configs/${server.id}`, { config });
|
||||
await api.post(`/server-configs/${server.id}`, { config });
|
||||
|
||||
results.push({ server: server.name, status: 'success' });
|
||||
} catch (error) {
|
||||
@@ -408,7 +408,7 @@ function FilterManager() {
|
||||
|
||||
// Сохраняем фильтры сервера
|
||||
try {
|
||||
await axios.post(`${API_URL}/server-filters/${selectedServer.id}`, { filters: updatedFilters });
|
||||
await api.post(`/server-filters/${selectedServer.id}`, { filters: updatedFilters });
|
||||
setSuccess('Фильтр успешно добавлен!');
|
||||
setTimeout(() => setSuccess(''), 3000);
|
||||
} catch (error) {
|
||||
@@ -437,7 +437,7 @@ function FilterManager() {
|
||||
|
||||
// Сохраняем фильтры сервера
|
||||
try {
|
||||
await axios.post(`${API_URL}/server-filters/${selectedServer.id}`, { filters: updatedFilters });
|
||||
await api.post(`/server-filters/${selectedServer.id}`, { filters: updatedFilters });
|
||||
setSuccess('Фильтр успешно обновлен!');
|
||||
setTimeout(() => setSuccess(''), 3000);
|
||||
} catch (error) {
|
||||
@@ -658,7 +658,7 @@ function FilterManager() {
|
||||
cancelInlineEdit();
|
||||
setSelectedFilterKeys(new Set());
|
||||
try {
|
||||
await axios.post(`${API_URL}/server-filters/${selectedServer.id}`, { filters: updated });
|
||||
await api.post(`/server-filters/${selectedServer.id}`, { filters: updated });
|
||||
setSuccess('Фильтр успешно обновлен!');
|
||||
setTimeout(() => setSuccess(''), 3000);
|
||||
} catch (error) {
|
||||
@@ -696,7 +696,7 @@ function FilterManager() {
|
||||
setServerFilters(updated);
|
||||
setSelectedFilterKeys(new Set());
|
||||
try {
|
||||
await axios.post(`${API_URL}/server-filters/${selectedServer.id}`, { filters: updated });
|
||||
await api.post(`/server-filters/${selectedServer.id}`, { filters: updated });
|
||||
setSuccess('Выбранные фильтры удалены.');
|
||||
setTimeout(() => setSuccess(''), 3000);
|
||||
} catch (error) {
|
||||
@@ -719,9 +719,9 @@ function FilterManager() {
|
||||
try {
|
||||
// Копируем фильтры и генерируем конфиг для каждого сервера
|
||||
for (const server of servers) {
|
||||
await axios.post(`${API_URL}/server-filters/${server.id}`, { filters: simpleFilters });
|
||||
await api.post(`/server-filters/${server.id}`, { filters: simpleFilters });
|
||||
// Генерируем конфиг
|
||||
const response = await axios.post(`${API_URL}/server-filters/generate-config`, { filters: simpleFilters });
|
||||
const response = await api.post(`/server-filters/generate-config`, { filters: simpleFilters });
|
||||
if (response.data && response.data.config) {
|
||||
await axios.post(`${API_URL}/server-configs/${server.id}`, { config: response.data.config });
|
||||
}
|
||||
@@ -748,7 +748,7 @@ function FilterManager() {
|
||||
|
||||
// Сохраняем фильтры
|
||||
try {
|
||||
await axios.post(`${API_URL}/simple-filters`, { filters: updatedFilters });
|
||||
await api.post(`/simple-filters`, { filters: updatedFilters });
|
||||
setSimpleSuccess('Фильтр успешно добавлен!');
|
||||
setTimeout(() => setSimpleSuccess(''), 3000);
|
||||
} catch (error) {
|
||||
@@ -779,7 +779,7 @@ function FilterManager() {
|
||||
|
||||
// Сохраняем фильтры
|
||||
try {
|
||||
await axios.post(`${API_URL}/simple-filters`, { filters: updated });
|
||||
await api.post(`/simple-filters`, { filters: updated });
|
||||
} catch (error) {
|
||||
console.error('Error saving simple filters:', error);
|
||||
setSimpleError('Не удалось сохранить изменения.');
|
||||
@@ -1593,7 +1593,7 @@ function FilterManager() {
|
||||
className="btn btn-outline-primary"
|
||||
onClick={async () => {
|
||||
try {
|
||||
const response = await axios.post(`${API_URL}/server-filters/generate-config`, { filters: simpleFilters });
|
||||
const response = await api.post(`/server-filters/generate-config`, { filters: simpleFilters });
|
||||
if (response.data && response.data.config) {
|
||||
setGeneratedConfig(response.data.config);
|
||||
setPreviewMode('simple');
|
||||
@@ -1614,7 +1614,7 @@ function FilterManager() {
|
||||
className="btn btn-outline-primary"
|
||||
onClick={async () => {
|
||||
try {
|
||||
const response = await axios.post(`${API_URL}/server-filters/generate-config`, { filters: simpleFilters });
|
||||
const response = await api.post(`/server-filters/generate-config`, { filters: simpleFilters });
|
||||
if (response.data && response.data.config) {
|
||||
const config = response.data.config;
|
||||
if (navigator.clipboard) {
|
||||
@@ -1664,7 +1664,7 @@ function FilterManager() {
|
||||
className="btn btn-outline-primary"
|
||||
onClick={async () => {
|
||||
try {
|
||||
await axios.post(`${API_URL}/simple-filters`, { filters: simpleFilters });
|
||||
await api.post(`/simple-filters`, { filters: simpleFilters });
|
||||
setSimpleSuccess('Фильтры успешно сохранены!');
|
||||
setTimeout(() => setSimpleSuccess(''), 3000);
|
||||
} catch (error) {
|
||||
@@ -1681,7 +1681,7 @@ function FilterManager() {
|
||||
onClick={async () => {
|
||||
if (window.confirm('Вы уверены, что хотите очистить все фильтры?')) {
|
||||
try {
|
||||
await axios.post(`${API_URL}/simple-filters`, { filters: [] });
|
||||
await api.post(`/simple-filters`, { filters: [] });
|
||||
setSimpleFilters([]);
|
||||
setSimpleSuccess('Все фильтры очищены!');
|
||||
setTimeout(() => setSimpleSuccess(''), 3000);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import axios from 'axios';
|
||||
import api from './lib/api.js';
|
||||
import {
|
||||
IconPlus,
|
||||
IconSearch,
|
||||
@@ -113,7 +113,7 @@ function ServerManager() {
|
||||
const fetchServers = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await axios.get(`${API_URL}/servers`);
|
||||
const response = await api.get(`/servers`);
|
||||
setServers(response.data);
|
||||
setError('');
|
||||
} catch (error) {
|
||||
@@ -316,7 +316,7 @@ function ServerManager() {
|
||||
const handleSaveChanges = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
await axios.post(`${API_URL}/servers`, { domains: servers });
|
||||
await api.post(`/servers`, { domains: servers });
|
||||
setSuccess('Изменения успешно сохранены!');
|
||||
setTimeout(() => setSuccess(''), 3000);
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import axios from 'axios';
|
||||
import api from '../lib/api.js';
|
||||
|
||||
function LockBanner({ resource, className = '' }) {
|
||||
const [status, setStatus] = useState({ locked: false });
|
||||
@@ -15,7 +15,7 @@ function LockBanner({ resource, className = '' }) {
|
||||
|
||||
const fetchStatus = async () => {
|
||||
try {
|
||||
const res = await axios.get(`/api/locks/${resource}`);
|
||||
const res = await api.get(`/locks/${resource}`);
|
||||
setStatus(res.data || { locked: false });
|
||||
} catch {
|
||||
// ignore
|
||||
@@ -31,7 +31,7 @@ function LockBanner({ resource, className = '' }) {
|
||||
const acquireOrRefresh = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await axios.post(`/api/locks/${resource}`, { owner, ttlSeconds: 180 });
|
||||
const res = await api.post(`/locks/${resource}`, { owner, ttlSeconds: 180 });
|
||||
setStatus(res.data || { locked: true, owner, expiresAt: Date.now() + 180_000 });
|
||||
} catch {
|
||||
// ignore
|
||||
@@ -43,7 +43,7 @@ function LockBanner({ resource, className = '' }) {
|
||||
const release = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
await axios.delete(`/api/locks/${resource}`);
|
||||
await api.delete(`/locks/${resource}`);
|
||||
setStatus({ locked: false });
|
||||
} catch {
|
||||
// ignore
|
||||
|
||||
@@ -4,4 +4,13 @@ import react from '@vitejs/plugin-react'
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
server: {
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:3001',
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user