Set up test prod db connection
This commit is contained in:
72
inventory-server/package-lock.json
generated
72
inventory-server/package-lock.json
generated
@@ -16,6 +16,7 @@
|
|||||||
"multer": "^1.4.5-lts.1",
|
"multer": "^1.4.5-lts.1",
|
||||||
"mysql2": "^3.12.0",
|
"mysql2": "^3.12.0",
|
||||||
"pm2": "^5.3.0",
|
"pm2": "^5.3.0",
|
||||||
|
"ssh2": "^1.16.0",
|
||||||
"uuid": "^9.0.1"
|
"uuid": "^9.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -367,6 +368,15 @@
|
|||||||
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
|
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/asn1": {
|
||||||
|
"version": "0.2.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
|
||||||
|
"integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"safer-buffer": "~2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ast-types": {
|
"node_modules/ast-types": {
|
||||||
"version": "0.13.4",
|
"version": "0.13.4",
|
||||||
"resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz",
|
"resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz",
|
||||||
@@ -416,6 +426,15 @@
|
|||||||
"node": ">=10.0.0"
|
"node": ">=10.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/bcrypt-pbkdf": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"tweetnacl": "^0.14.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/binary-extensions": {
|
"node_modules/binary-extensions": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
||||||
@@ -499,6 +518,15 @@
|
|||||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/buildcheck": {
|
||||||
|
"version": "0.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.6.tgz",
|
||||||
|
"integrity": "sha512-8f9ZJCUXyT1M35Jx7MkBgmBMo3oHTTBIPLiY9xyL0pl3T5RwcPEY8cUHr5LBNfu/fk6c2T4DJZuVM/8ZZT2D2A==",
|
||||||
|
"optional": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/busboy": {
|
"node_modules/busboy": {
|
||||||
"version": "1.6.0",
|
"version": "1.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
|
||||||
@@ -703,6 +731,20 @@
|
|||||||
"node": ">= 0.10"
|
"node": ">= 0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/cpu-features": {
|
||||||
|
"version": "0.0.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.10.tgz",
|
||||||
|
"integrity": "sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"buildcheck": "~0.0.6",
|
||||||
|
"nan": "^2.19.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/croner": {
|
"node_modules/croner": {
|
||||||
"version": "4.1.97",
|
"version": "4.1.97",
|
||||||
"resolved": "https://registry.npmjs.org/croner/-/croner-4.1.97.tgz",
|
"resolved": "https://registry.npmjs.org/croner/-/croner-4.1.97.tgz",
|
||||||
@@ -1758,6 +1800,13 @@
|
|||||||
"node": ">=12.0.0"
|
"node": ">=12.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/nan": {
|
||||||
|
"version": "2.22.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/nan/-/nan-2.22.0.tgz",
|
||||||
|
"integrity": "sha512-nbajikzWTMwsW+eSsNm3QwlOs7het9gGJU5dDZzRTQGk03vyBOauxgI4VakDzE0PtsGTmXPsXTbbjVhRwR5mpw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"node_modules/needle": {
|
"node_modules/needle": {
|
||||||
"version": "2.4.0",
|
"version": "2.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/needle/-/needle-2.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/needle/-/needle-2.4.0.tgz",
|
||||||
@@ -2816,6 +2865,23 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ssh2": {
|
||||||
|
"version": "1.16.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.16.0.tgz",
|
||||||
|
"integrity": "sha512-r1X4KsBGedJqo7h8F5c4Ybpcr5RjyP+aWIG007uBPRjmdQWfEiVLzSK71Zji1B9sKxwaCvD8y8cwSkYrlLiRRg==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"dependencies": {
|
||||||
|
"asn1": "^0.2.6",
|
||||||
|
"bcrypt-pbkdf": "^1.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.16.0"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"cpu-features": "~0.0.10",
|
||||||
|
"nan": "^2.20.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/statuses": {
|
"node_modules/statuses": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||||
@@ -2954,6 +3020,12 @@
|
|||||||
"node": ">= 0.8.0"
|
"node": ">= 0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tweetnacl": {
|
||||||
|
"version": "0.14.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
|
||||||
|
"integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==",
|
||||||
|
"license": "Unlicense"
|
||||||
|
},
|
||||||
"node_modules/tx2": {
|
"node_modules/tx2": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/tx2/-/tx2-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/tx2/-/tx2-1.0.5.tgz",
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
"multer": "^1.4.5-lts.1",
|
"multer": "^1.4.5-lts.1",
|
||||||
"mysql2": "^3.12.0",
|
"mysql2": "^3.12.0",
|
||||||
"pm2": "^5.3.0",
|
"pm2": "^5.3.0",
|
||||||
|
"ssh2": "^1.16.0",
|
||||||
"uuid": "^9.0.1"
|
"uuid": "^9.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
89
inventory-server/scripts/test-prod-connection.js
Normal file
89
inventory-server/scripts/test-prod-connection.js
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
const mysql = require('mysql2/promise');
|
||||||
|
const { Client } = require('ssh2');
|
||||||
|
const dotenv = require('dotenv');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
dotenv.config({ path: path.join(__dirname, '../.env') });
|
||||||
|
|
||||||
|
// SSH configuration
|
||||||
|
const sshConfig = {
|
||||||
|
host: process.env.PROD_SSH_HOST,
|
||||||
|
port: process.env.PROD_SSH_PORT || 22,
|
||||||
|
username: process.env.PROD_SSH_USER,
|
||||||
|
privateKey: process.env.PROD_SSH_KEY_PATH ? require('fs').readFileSync(process.env.PROD_SSH_KEY_PATH) : undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
// Database configuration
|
||||||
|
const dbConfig = {
|
||||||
|
host: process.env.PROD_DB_HOST || 'localhost', // Usually localhost when tunneling
|
||||||
|
user: process.env.PROD_DB_USER,
|
||||||
|
password: process.env.PROD_DB_PASSWORD,
|
||||||
|
database: process.env.PROD_DB_NAME,
|
||||||
|
port: process.env.PROD_DB_PORT || 3306
|
||||||
|
};
|
||||||
|
|
||||||
|
async function testConnection() {
|
||||||
|
const ssh = new Client();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Create new Promise for SSH connection
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
ssh.on('ready', resolve)
|
||||||
|
.on('error', reject)
|
||||||
|
.connect(sshConfig);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('SSH Connection successful!');
|
||||||
|
|
||||||
|
// Forward local port to remote MySQL port
|
||||||
|
const tunnel = await new Promise((resolve, reject) => {
|
||||||
|
ssh.forwardOut(
|
||||||
|
'127.0.0.1',
|
||||||
|
0,
|
||||||
|
dbConfig.host,
|
||||||
|
dbConfig.port,
|
||||||
|
(err, stream) => {
|
||||||
|
if (err) reject(err);
|
||||||
|
resolve(stream);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Port forwarding established');
|
||||||
|
|
||||||
|
// Create MySQL connection over SSH tunnel
|
||||||
|
const connection = await mysql.createConnection({
|
||||||
|
...dbConfig,
|
||||||
|
stream: tunnel
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('MySQL Connection successful!');
|
||||||
|
|
||||||
|
// Test query
|
||||||
|
const [rows] = await connection.query('SELECT COUNT(*) as count FROM products');
|
||||||
|
console.log('Query successful! Product count:', rows[0].count);
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
await connection.end();
|
||||||
|
ssh.end();
|
||||||
|
console.log('Connections closed successfully');
|
||||||
|
return rows[0].count;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error:', error);
|
||||||
|
if (ssh) ssh.end();
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If running directly (not imported)
|
||||||
|
if (require.main === module) {
|
||||||
|
testConnection()
|
||||||
|
.then(() => process.exit(0))
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Test failed:', error);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { testConnection };
|
||||||
22
inventory-server/src/routes/test-connection.js
Normal file
22
inventory-server/src/routes/test-connection.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const { testConnection } = require('../../scripts/test-prod-connection');
|
||||||
|
|
||||||
|
router.get('/test-prod-connection', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const productCount = await testConnection();
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
message: 'Successfully connected to production database',
|
||||||
|
productCount
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Production connection test failed:', error);
|
||||||
|
res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
error: error.message || 'Failed to connect to production database'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
@@ -16,6 +16,7 @@ const configRouter = require('./routes/config');
|
|||||||
const metricsRouter = require('./routes/metrics');
|
const metricsRouter = require('./routes/metrics');
|
||||||
const vendorsRouter = require('./routes/vendors');
|
const vendorsRouter = require('./routes/vendors');
|
||||||
const categoriesRouter = require('./routes/categories');
|
const categoriesRouter = require('./routes/categories');
|
||||||
|
const testConnectionRouter = require('./routes/test-connection');
|
||||||
|
|
||||||
// Get the absolute path to the .env file
|
// Get the absolute path to the .env file
|
||||||
const envPath = path.resolve(process.cwd(), '.env');
|
const envPath = path.resolve(process.cwd(), '.env');
|
||||||
@@ -91,6 +92,7 @@ app.use('/api/config', configRouter);
|
|||||||
app.use('/api/metrics', metricsRouter);
|
app.use('/api/metrics', metricsRouter);
|
||||||
app.use('/api/vendors', vendorsRouter);
|
app.use('/api/vendors', vendorsRouter);
|
||||||
app.use('/api/categories', categoriesRouter);
|
app.use('/api/categories', categoriesRouter);
|
||||||
|
app.use('/api', testConnectionRouter);
|
||||||
|
|
||||||
// Basic health check route
|
// Basic health check route
|
||||||
app.get('/health', (req, res) => {
|
app.get('/health', (req, res) => {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
AlertDialogTitle,
|
AlertDialogTitle,
|
||||||
AlertDialogTrigger,
|
AlertDialogTrigger,
|
||||||
} from "@/components/ui/alert-dialog";
|
} from "@/components/ui/alert-dialog";
|
||||||
import { Loader2, RefreshCw, Upload, X } from "lucide-react";
|
import { Loader2, RefreshCw, Upload, X, Database } from "lucide-react";
|
||||||
import config from '../../config';
|
import config from '../../config';
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
|
||||||
@@ -71,6 +71,9 @@ export function DataManagement() {
|
|||||||
// Track cancellation state
|
// Track cancellation state
|
||||||
const [cancelledOperations, setCancelledOperations] = useState<Set<string>>(new Set());
|
const [cancelledOperations, setCancelledOperations] = useState<Set<string>>(new Set());
|
||||||
|
|
||||||
|
// Add new state for testing connection
|
||||||
|
const [isTestingConnection, setIsTestingConnection] = useState(false);
|
||||||
|
|
||||||
// Helper to check if any operation is running
|
// Helper to check if any operation is running
|
||||||
const isAnyOperationRunning = () => {
|
const isAnyOperationRunning = () => {
|
||||||
return isUpdating || isImporting || isResetting || isResettingMetrics || isCalculatingMetrics;
|
return isUpdating || isImporting || isResetting || isResettingMetrics || isCalculatingMetrics;
|
||||||
@@ -829,8 +832,56 @@ export function DataManagement() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleTestConnection = async () => {
|
||||||
|
setIsTestingConnection(true);
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${config.apiUrl}/test-prod-connection`, {
|
||||||
|
credentials: 'include'
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
toast.success(`Successfully connected to production database. Found ${data.productCount.toLocaleString()} products.`);
|
||||||
|
} else {
|
||||||
|
throw new Error(data.error || 'Failed to connect to production database');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
toast.error(`Connection test failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
||||||
|
} finally {
|
||||||
|
setIsTestingConnection(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-[400px] space-y-4">
|
<div className="max-w-[400px] space-y-4">
|
||||||
|
{/* Test Production Connection Card */}
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Test Production Connection</CardTitle>
|
||||||
|
<CardDescription>Verify connection to production database</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<Button
|
||||||
|
className="w-full"
|
||||||
|
onClick={handleTestConnection}
|
||||||
|
disabled={isTestingConnection || isAnyOperationRunning()}
|
||||||
|
>
|
||||||
|
{isTestingConnection ? (
|
||||||
|
<>
|
||||||
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||||
|
Testing Connection...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Database className="mr-2 h-4 w-4" />
|
||||||
|
Test Production Connection
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
{/* Update CSV Card */}
|
{/* Update CSV Card */}
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
|
|||||||
Reference in New Issue
Block a user