feat: Add billing routes in server.js for retrieving and updating billing data, and integrate BillingManager in App.jsx for navigation
Publish Fast Tabler Docker image / build-and-push-fast (push) Failing after 6m7s
Publish Fast Tabler Docker image / build-and-push-fast (push) Failing after 6m7s
This commit is contained in:
@@ -295,6 +295,75 @@ app.post('/api/servers', async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// --- Billing Routes ---
|
||||||
|
|
||||||
|
// Get billing data from S3
|
||||||
|
app.get('/api/billing', async (req, res) => {
|
||||||
|
const params = {
|
||||||
|
Bucket: BUCKET_NAME,
|
||||||
|
Key: 'servers-billing.json',
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await s3.getObject(params).promise();
|
||||||
|
const fileContent = data.Body.toString('utf-8');
|
||||||
|
let billingData = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
billingData = JSON.parse(fileContent);
|
||||||
|
// Ensure it's an array
|
||||||
|
if (!Array.isArray(billingData)) {
|
||||||
|
billingData = [];
|
||||||
|
}
|
||||||
|
} catch (parseError) {
|
||||||
|
console.error('Error parsing servers-billing.json:', parseError);
|
||||||
|
billingData = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json(billingData);
|
||||||
|
} catch (error) {
|
||||||
|
if (error.code === 'NoSuchKey') {
|
||||||
|
res.json([]); // Return empty array if file does not exist
|
||||||
|
} else {
|
||||||
|
console.error(error);
|
||||||
|
res.status(500).send('Error reading from S3');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update billing data in S3
|
||||||
|
app.post('/api/billing', async (req, res) => {
|
||||||
|
const { domains: billingData } = req.body; // Keep name 'domains' for consistency
|
||||||
|
|
||||||
|
// Validate billing data structure
|
||||||
|
if (!Array.isArray(billingData)) {
|
||||||
|
return res.status(400).send('Billing data must be an array');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate each billing item has required fields
|
||||||
|
for (let i = 0; i < billingData.length; i++) {
|
||||||
|
const item = billingData[i];
|
||||||
|
if (!item.hostName || !item.nodeName || !item.country || !item.provider) {
|
||||||
|
return res.status(400).send(`Billing item at index ${i} is missing required fields`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
Bucket: BUCKET_NAME,
|
||||||
|
Key: 'servers-billing.json',
|
||||||
|
Body: JSON.stringify(billingData, null, 2), // Pretty print JSON
|
||||||
|
ContentType: 'application/json',
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
await s3.putObject(params).promise();
|
||||||
|
res.send('File updated successfully');
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
res.status(500).send('Error writing to S3');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// --- Filters Routes (JSON format) ---
|
// --- Filters Routes (JSON format) ---
|
||||||
|
|
||||||
// Get filters from S3
|
// Get filters from S3
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "1",
|
||||||
|
"hostName": "VDSINA",
|
||||||
|
"nodeName": "VDSINA",
|
||||||
|
"country": "RU",
|
||||||
|
"provider": "VDSINA",
|
||||||
|
"loginUrl": "cp.vdsina.com",
|
||||||
|
"monthlyCost": 12.00,
|
||||||
|
"nextPaymentDate": "2026-04-25",
|
||||||
|
"lastPaymentDate": "2025-05-05",
|
||||||
|
"lastPaymentAmount": 12.00,
|
||||||
|
"status": "active",
|
||||||
|
"notes": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "2",
|
||||||
|
"hostName": "Macloud",
|
||||||
|
"nodeName": "Macloud",
|
||||||
|
"country": "RU",
|
||||||
|
"provider": "Macloud",
|
||||||
|
"loginUrl": "cp.macloud.ru",
|
||||||
|
"monthlyCost": 7.00,
|
||||||
|
"nextPaymentDate": "2025-09-02",
|
||||||
|
"lastPaymentDate": "2025-02-03",
|
||||||
|
"lastPaymentAmount": 7.00,
|
||||||
|
"status": "active",
|
||||||
|
"notes": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "3",
|
||||||
|
"hostName": "Hosting VDS",
|
||||||
|
"nodeName": "Steal",
|
||||||
|
"country": "RU",
|
||||||
|
"provider": "Hosting VDS",
|
||||||
|
"loginUrl": "my.hosting-vds.com/",
|
||||||
|
"monthlyCost": 11.95,
|
||||||
|
"nextPaymentDate": "2025-12-21",
|
||||||
|
"lastPaymentDate": "2025-06-13",
|
||||||
|
"lastPaymentAmount": 10.00,
|
||||||
|
"status": "active",
|
||||||
|
"notes": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "4",
|
||||||
|
"hostName": "Waicore",
|
||||||
|
"nodeName": "Waicore",
|
||||||
|
"country": "DE",
|
||||||
|
"provider": "Waicore",
|
||||||
|
"loginUrl": "my.waicore.com/billmgr",
|
||||||
|
"monthlyCost": 9.60,
|
||||||
|
"nextPaymentDate": "2026-04-25",
|
||||||
|
"lastPaymentDate": "2025-04-25",
|
||||||
|
"lastPaymentAmount": 9.60,
|
||||||
|
"status": "active",
|
||||||
|
"notes": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "5",
|
||||||
|
"hostName": "IHOR",
|
||||||
|
"nodeName": "Ihore",
|
||||||
|
"country": "RU",
|
||||||
|
"provider": "IHOR",
|
||||||
|
"loginUrl": "billing.ihor-hosting.ru/billmgr",
|
||||||
|
"monthlyCost": 10.00,
|
||||||
|
"nextPaymentDate": "2025-09-10",
|
||||||
|
"lastPaymentDate": "2025-06-25",
|
||||||
|
"lastPaymentAmount": 10.00,
|
||||||
|
"status": "active",
|
||||||
|
"notes": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -27,6 +27,7 @@ import DomainsNewManager from './DomainsNewManager';
|
|||||||
import IPRangesManager from './IPRangesManager';
|
import IPRangesManager from './IPRangesManager';
|
||||||
import ASNsNewManager from './ASNsNewManager';
|
import ASNsNewManager from './ASNsNewManager';
|
||||||
import AutoUrlManager from './AutoUrlManager';
|
import AutoUrlManager from './AutoUrlManager';
|
||||||
|
import BillingManager from './BillingManager';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import {
|
import {
|
||||||
@@ -60,6 +61,7 @@ function MainLayout() {
|
|||||||
{ id: 'asns', title: 'AS', icon: IconNetwork, path: '/asns' },
|
{ id: 'asns', title: 'AS', icon: IconNetwork, path: '/asns' },
|
||||||
{ id: 'auto-urls', title: 'Авто URL', icon: IconDownload, path: '/auto-urls' },
|
{ id: 'auto-urls', title: 'Авто URL', icon: IconDownload, path: '/auto-urls' },
|
||||||
{ id: 'servers', title: 'Серверы', icon: IconServer, path: '/servers' },
|
{ id: 'servers', title: 'Серверы', icon: IconServer, path: '/servers' },
|
||||||
|
{ id: 'billing', title: 'Биллинг', icon: IconCreditCard, path: '/billing' },
|
||||||
{ id: 'filters', title: 'Фильтры', icon: IconFilter, path: '/filters' },
|
{ id: 'filters', title: 'Фильтры', icon: IconFilter, path: '/filters' },
|
||||||
{ id: 'files', title: 'Файлы', icon: IconFileText, path: '/files' },
|
{ id: 'files', title: 'Файлы', icon: IconFileText, path: '/files' },
|
||||||
{ id: 'cloud', title: 'Облако', icon: IconCloud, path: '/cloud' },
|
{ id: 'cloud', title: 'Облако', icon: IconCloud, path: '/cloud' },
|
||||||
@@ -108,6 +110,7 @@ function MainLayout() {
|
|||||||
<Route path="/asns" element={<ASNsNewManager />} />
|
<Route path="/asns" element={<ASNsNewManager />} />
|
||||||
<Route path="/auto-urls" element={<AutoUrlManager />} />
|
<Route path="/auto-urls" element={<AutoUrlManager />} />
|
||||||
<Route path="/servers" element={<ServerManager />} />
|
<Route path="/servers" element={<ServerManager />} />
|
||||||
|
<Route path="/billing" element={<BillingManager />} />
|
||||||
<Route path="/filters" element={<FilterManager />} />
|
<Route path="/filters" element={<FilterManager />} />
|
||||||
<Route path="/" element={<Navigate to="/domains" replace />} />
|
<Route path="/" element={<Navigate to="/domains" replace />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user