Fix mock tests
This commit is contained in:
4
pom.xml
4
pom.xml
@@ -290,6 +290,10 @@
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-config-yaml</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-undertow</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- OSGi annotations -->
|
||||
<dependency>
|
||||
|
||||
@@ -44,8 +44,9 @@ class ImageProcessingService {
|
||||
var response = httpClient.sendGetBytes(imageUrl);
|
||||
|
||||
if (response != null && response.statusCode() == 200) {
|
||||
// Use Windows path: C:\mnt\okcomputer\output\images
|
||||
var baseDir = Paths.get("C:", "mnt", "okcomputer", "output", "images");
|
||||
// Use environment variable for cross-platform compatibility
|
||||
var imagesPath = System.getenv().getOrDefault("AUCTION_IMAGES_PATH", "/mnt/okcomputer/output/images");
|
||||
var baseDir = Paths.get(imagesPath);
|
||||
var dir = baseDir.resolve(String.valueOf(saleId)).resolve(String.valueOf(lotId));
|
||||
Files.createDirectories(dir);
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="https://jakarta.ee/xml/ns/jakartaee"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/beans_3_0.xsd
|
||||
https://jakarta.ee/xml/ns/jakartaee "
|
||||
version="3.0" bean-discovery-mode="all">
|
||||
</beans>
|
||||
@@ -1,224 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Scrape-UI 1 - Enterprise</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<style>
|
||||
.gradient-bg {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
}
|
||||
|
||||
.card-hover {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.card-hover:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gray-50 min-h-screen">
|
||||
<!-- Header -->
|
||||
<header class="gradient-bg text-white py-8">
|
||||
<div class="container mx-auto px-4">
|
||||
<h1 class="text-4xl font-bold mb-2">Scrape-UI Enterprise</h1>
|
||||
<p class="text-xl opacity-90">Powered by Quarkus + Modern Frontend</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="container mx-auto px-4 py-8">
|
||||
<!-- API Status Card -->
|
||||
<!-- API & Build Status Card -->
|
||||
<div class="bg-white rounded-lg shadow-md p-6 mb-8 card-hover">
|
||||
<h2 class="text-2xl font-bold mb-4 text-gray-800">Build & Runtime Status</h2>
|
||||
<div id="api-status" class="space-y-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<!-- Build Information -->
|
||||
<div class="bg-blue-50 p-4 rounded-lg">
|
||||
<h3 class="font-semibold text-blue-800 mb-2">📦 Maven Build</h3>
|
||||
<div class="space-y-2 text-sm">
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-600">Group:</span>
|
||||
<span class="font-mono font-medium" id="build-group">-</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-600">Artifact:</span>
|
||||
<span class="font-mono font-medium" id="build-artifact">-</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-600">Version:</span>
|
||||
<span class="font-mono font-medium px-2 py-1 bg-blue-100 rounded" id="build-version">-</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Runtime Information -->
|
||||
<div class="bg-green-50 p-4 rounded-lg">
|
||||
<h3 class="font-semibold text-green-800 mb-2">🚀 Runtime</h3>
|
||||
<div class="space-y-2 text-sm">
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-600">Status:</span>
|
||||
<span class="px-2 py-1 rounded-full text-xs font-semibold bg-green-100 text-green-800" id="runtime-status">-</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-600">Java:</span>
|
||||
<span class="font-mono" id="java-version">-</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-600">Platform:</span>
|
||||
<span class="font-mono" id="runtime-os">-</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Timestamp & Additional Info -->
|
||||
<div class="pt-4 border-t">
|
||||
<div class="flex justify-between items-center">
|
||||
<div>
|
||||
<p class="text-sm text-gray-500">Last Updated</p>
|
||||
<p class="font-medium" id="last-updated">-</p>
|
||||
</div>
|
||||
<button onclick="fetchStatus()" class="bg-gray-100 hover:bg-gray-200 text-gray-700 px-4 py-2 rounded-lg text-sm transition-colors">
|
||||
🔄 Refresh
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- API Response Card -->
|
||||
<div class="bg-white rounded-lg shadow-md p-6 card-hover">
|
||||
<h2 class="text-2xl font-bold mb-4 text-gray-800">API Test</h2>
|
||||
<button id="test-api" class="bg-blue-600 text-white px-6 py-2 rounded-lg hover:bg-blue-700 transition-colors mb-4">
|
||||
Test Greeting API
|
||||
</button>
|
||||
<div id="api-response" class="bg-gray-100 p-4 rounded-lg">
|
||||
<pre class="text-sm text-gray-700">Click the button to test the API</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Features Grid -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mt-8">
|
||||
<div class="bg-white rounded-lg shadow-md p-6 card-hover">
|
||||
<h3 class="text-xl font-semibold mb-2 text-gray-800">⚡ Quarkus Backend</h3>
|
||||
<p class="text-gray-600">Fast startup, low memory footprint, optimized for containers</p>
|
||||
</div>
|
||||
<div class="bg-white rounded-lg shadow-md p-6 card-hover">
|
||||
<h3 class="text-xl font-semibold mb-2 text-gray-800">🚀 REST API</h3>
|
||||
<p class="text-gray-600">RESTful endpoints with JSON responses</p>
|
||||
</div>
|
||||
<div class="bg-white rounded-lg shadow-md p-6 card-hover">
|
||||
<h3 class="text-xl font-semibold mb-2 text-gray-800">🎨 Modern UI</h3>
|
||||
<p class="text-gray-600">Responsive design with Tailwind CSS</p>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
// Fetch API status on load
|
||||
async function fetchStatus() {
|
||||
try {
|
||||
const response = await fetch('/api/status')
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${ response.status }: ${ response.statusText }`)
|
||||
}
|
||||
const data = await response.json()
|
||||
|
||||
// Update Build Information
|
||||
document.getElementById('build-group').textContent = data.groupId || 'N/A'
|
||||
document.getElementById('build-artifact').textContent = data.artifactId || data.name || 'N/A'
|
||||
document.getElementById('build-version').textContent = data.version || 'N/A'
|
||||
|
||||
// Update Runtime Information
|
||||
document.getElementById('runtime-status').textContent = data.status || 'unknown'
|
||||
document.getElementById('java-version').textContent = data.javaVersion || System.getProperty?.('java.version') || 'N/A'
|
||||
document.getElementById('runtime-os').textContent = data.os || 'N/A'
|
||||
|
||||
// Update Timestamp
|
||||
const timestamp = data.timestamp ? new Date(data.timestamp).toLocaleString() : 'N/A'
|
||||
document.getElementById('last-updated').textContent = timestamp
|
||||
|
||||
// Update status badge color based on status
|
||||
const statusBadge = document.getElementById('runtime-status')
|
||||
if (data.status?.toLowerCase() === 'running') {
|
||||
statusBadge.className = 'px-2 py-1 rounded-full text-xs font-semibold bg-green-100 text-green-800'
|
||||
} else {
|
||||
statusBadge.className = 'px-2 py-1 rounded-full text-xs font-semibold bg-yellow-100 text-yellow-800'
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error fetching status:', error)
|
||||
document.getElementById('api-status').innerHTML = `
|
||||
<div class="bg-red-50 border-l-4 border-red-500 p-4">
|
||||
<div class="flex">
|
||||
<div class="flex-shrink-0">
|
||||
<svg class="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<p class="text-sm text-red-700">Failed to load status: ${ error.message }</p>
|
||||
<button onclick="fetchStatus()" class="mt-2 text-sm text-red-700 hover:text-red-600 font-medium">
|
||||
Retry →
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch API status on load
|
||||
async function fetchStatus3() {
|
||||
try {
|
||||
const response = await fetch('/api/status')
|
||||
const data = await response.json()
|
||||
document.getElementById('api-status').innerHTML = `
|
||||
<p><strong>Application:</strong> ${ data.application }</p>
|
||||
<p><strong>Status:</strong> <span class="text-green-600 font-semibold">${ data.status }</span></p>
|
||||
<p><strong>Version:</strong> ${ data.version }</p>
|
||||
<p><strong>Timestamp:</strong> ${ data.timestamp }</p>
|
||||
`
|
||||
} catch (error) {
|
||||
document.getElementById('api-status').innerHTML = `
|
||||
<p class="text-red-600">Error loading status: ${ error.message }</p>
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
// Test greeting API
|
||||
document.getElementById('test-api').addEventListener('click', async () => {
|
||||
try {
|
||||
const response = await fetch('/api/hello')
|
||||
const data = await response.json()
|
||||
document.getElementById('api-response').innerHTML = `
|
||||
<pre class="text-sm text-gray-700">${ JSON.stringify(data, null, 2) }</pre>
|
||||
`
|
||||
} catch (error) {
|
||||
document.getElementById('api-response').innerHTML = `
|
||||
<pre class="text-sm text-red-600">Error: ${ error.message }</pre>
|
||||
`
|
||||
}
|
||||
})
|
||||
// Auto-refresh every 30 seconds
|
||||
let refreshInterval = setInterval(fetchStatus, 30000);
|
||||
|
||||
// Stop auto-refresh when page loses focus (optional)
|
||||
document.addEventListener('visibilitychange', function() {
|
||||
if (document.hidden) {
|
||||
clearInterval(refreshInterval);
|
||||
} else {
|
||||
refreshInterval = setInterval(fetchStatus, 30000);
|
||||
fetchStatus(); // Refresh immediately when returning to tab
|
||||
}
|
||||
});
|
||||
// Load status on page load
|
||||
fetchStatus()
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,572 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Auctiora - Monitoring Dashboard</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/plotly.js/3.0.3/plotly.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
||||
<style>
|
||||
.gradient-bg {
|
||||
background: linear-gradient(135deg, #1e3a8a 0%, #3b82f6 50%, #60a5fa 100%);
|
||||
}
|
||||
.card-hover {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.card-hover:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
.status-online {
|
||||
animation: pulse-green 2s infinite;
|
||||
}
|
||||
@keyframes pulse-green {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
}
|
||||
.workflow-card {
|
||||
background: linear-gradient(145deg, #ffffff 0%, #f8fafc 100%);
|
||||
border-left: 4px solid #3b82f6;
|
||||
}
|
||||
.alert-badge {
|
||||
animation: pulse-red 1.5s infinite;
|
||||
}
|
||||
@keyframes pulse-red {
|
||||
0%, 100% { transform: scale(1); }
|
||||
50% { transform: scale(1.1); }
|
||||
}
|
||||
.metric-card {
|
||||
background: linear-gradient(145deg, #ffffff 0%, #fafbfc 100%);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gray-50 min-h-screen">
|
||||
<!-- Header -->
|
||||
<header class="gradient-bg text-white py-6 shadow-lg">
|
||||
<div class="container mx-auto px-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-4xl font-bold mb-2">
|
||||
<i class="fas fa-cube mr-3"></i>
|
||||
Auctiora Monitoring Dashboard
|
||||
</h1>
|
||||
<p class="text-lg opacity-90">Real-time Auction Data Processing & Monitoring</p>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<div class="flex items-center space-x-2">
|
||||
<span class="status-online inline-block w-3 h-3 bg-green-400 rounded-full"></span>
|
||||
<span class="text-sm font-semibold">System Online</span>
|
||||
</div>
|
||||
<div class="text-xs opacity-75 mt-1" id="last-update">Last updated: --:--</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="container mx-auto px-4 py-8">
|
||||
|
||||
<!-- System Status Cards -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-4 mb-8">
|
||||
<div class="bg-white rounded-lg shadow-md p-6 card-hover metric-card">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<div class="text-gray-600 text-sm font-semibold uppercase">Auctions</div>
|
||||
<div class="text-3xl font-bold text-blue-600 mt-2" id="total-auctions">--</div>
|
||||
</div>
|
||||
<div class="p-4 rounded-full bg-blue-100">
|
||||
<i class="fas fa-gavel text-2xl text-blue-600"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-lg shadow-md p-6 card-hover metric-card">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<div class="text-gray-600 text-sm font-semibold uppercase">Total Lots</div>
|
||||
<div class="text-3xl font-bold text-green-600 mt-2" id="total-lots">--</div>
|
||||
</div>
|
||||
<div class="p-4 rounded-full bg-green-100">
|
||||
<i class="fas fa-boxes text-2xl text-green-600"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-lg shadow-md p-6 card-hover metric-card">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<div class="text-gray-600 text-sm font-semibold uppercase">Images</div>
|
||||
<div class="text-3xl font-bold text-purple-600 mt-2" id="total-images">--</div>
|
||||
</div>
|
||||
<div class="p-4 rounded-full bg-purple-100">
|
||||
<i class="fas fa-images text-2xl text-purple-600"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-lg shadow-md p-6 card-hover metric-card">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<div class="text-gray-600 text-sm font-semibold uppercase">Active Lots</div>
|
||||
<div class="text-3xl font-bold text-yellow-600 mt-2" id="active-lots">--</div>
|
||||
</div>
|
||||
<div class="p-4 rounded-full bg-yellow-100">
|
||||
<i class="fas fa-clock text-2xl text-yellow-600"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-lg shadow-md p-6 card-hover metric-card">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<div class="text-gray-600 text-sm font-semibold uppercase">Closing Soon</div>
|
||||
<div class="text-3xl font-bold text-red-600 mt-2" id="closing-soon">--</div>
|
||||
</div>
|
||||
<div class="p-4 rounded-full bg-red-100">
|
||||
<i class="fas fa-bell text-2xl text-red-600"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Statistics Overview -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8">
|
||||
<div class="bg-white rounded-lg shadow-md p-6 col-span-2">
|
||||
<h3 class="text-xl font-semibold mb-4 text-gray-800">
|
||||
<i class="fas fa-chart-line mr-2 text-blue-600"></i>
|
||||
Bidding Statistics
|
||||
</h3>
|
||||
<div class="grid grid-cols-3 gap-4">
|
||||
<div class="text-center p-4 bg-blue-50 rounded-lg">
|
||||
<div class="text-gray-600 text-sm font-semibold">Lots with Bids</div>
|
||||
<div class="text-2xl font-bold text-blue-600 mt-2" id="lots-with-bids">--</div>
|
||||
</div>
|
||||
<div class="text-center p-4 bg-green-50 rounded-lg">
|
||||
<div class="text-gray-600 text-sm font-semibold">Total Bid Value</div>
|
||||
<div class="text-2xl font-bold text-green-600 mt-2" id="total-bid-value">--</div>
|
||||
</div>
|
||||
<div class="text-center p-4 bg-purple-50 rounded-lg">
|
||||
<div class="text-gray-600 text-sm font-semibold">Average Bid</div>
|
||||
<div class="text-2xl font-bold text-purple-600 mt-2" id="average-bid">--</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-lg shadow-md p-6">
|
||||
<h3 class="text-xl font-semibold mb-4 text-gray-800">
|
||||
<i class="fas fa-tachometer-alt mr-2 text-green-600"></i>
|
||||
Rate Limiting
|
||||
</h3>
|
||||
<div class="space-y-3">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-gray-600 text-sm">Total Requests</span>
|
||||
<span class="font-bold text-gray-800" id="rate-total-requests">--</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-gray-600 text-sm">Successful</span>
|
||||
<span class="font-bold text-green-600" id="rate-successful">--</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-gray-600 text-sm">Failed</span>
|
||||
<span class="font-bold text-red-600" id="rate-failed">--</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-gray-600 text-sm">Avg Duration</span>
|
||||
<span class="font-bold text-blue-600" id="rate-avg-duration">--</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Workflow Controls -->
|
||||
<div class="bg-white rounded-lg shadow-md p-6 mb-8">
|
||||
<h3 class="text-xl font-semibold mb-4 text-gray-800">
|
||||
<i class="fas fa-play-circle mr-2 text-blue-600"></i>
|
||||
Workflow Controls
|
||||
</h3>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<button onclick="triggerWorkflow('scraper-import')"
|
||||
class="workflow-card p-4 rounded-lg hover:shadow-lg transition-all">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<i class="fas fa-download text-blue-600 text-xl"></i>
|
||||
<span class="text-xs text-gray-500" id="status-scraper">Ready</span>
|
||||
</div>
|
||||
<div class="text-sm font-semibold text-gray-800">Import Scraper Data</div>
|
||||
<div class="text-xs text-gray-600 mt-1">Load auction/lot data from external scraper</div>
|
||||
</button>
|
||||
|
||||
<button onclick="triggerWorkflow('image-processing')"
|
||||
class="workflow-card p-4 rounded-lg hover:shadow-lg transition-all">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<i class="fas fa-image text-purple-600 text-xl"></i>
|
||||
<span class="text-xs text-gray-500" id="status-images">Ready</span>
|
||||
</div>
|
||||
<div class="text-sm font-semibold text-gray-800">Process Images</div>
|
||||
<div class="text-xs text-gray-600 mt-1">Download & analyze images with object detection</div>
|
||||
</button>
|
||||
|
||||
<button onclick="triggerWorkflow('bid-monitoring')"
|
||||
class="workflow-card p-4 rounded-lg hover:shadow-lg transition-all">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<i class="fas fa-chart-line text-green-600 text-xl"></i>
|
||||
<span class="text-xs text-gray-500" id="status-bids">Ready</span>
|
||||
</div>
|
||||
<div class="text-sm font-semibold text-gray-800">Monitor Bids</div>
|
||||
<div class="text-xs text-gray-600 mt-1">Track bid changes and send notifications</div>
|
||||
</button>
|
||||
|
||||
<button onclick="triggerWorkflow('closing-alerts')"
|
||||
class="workflow-card p-4 rounded-lg hover:shadow-lg transition-all">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<i class="fas fa-bell text-red-600 text-xl"></i>
|
||||
<span class="text-xs text-gray-500" id="status-alerts">Ready</span>
|
||||
</div>
|
||||
<div class="text-sm font-semibold text-gray-800">Closing Alerts</div>
|
||||
<div class="text-xs text-gray-600 mt-1">Alert for lots closing within 30 minutes</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Charts Section -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8 mb-8">
|
||||
<!-- Country Distribution -->
|
||||
<div class="bg-white rounded-lg shadow-md p-6">
|
||||
<h3 class="text-xl font-semibold mb-4 text-gray-800">
|
||||
<i class="fas fa-globe mr-2 text-blue-600"></i>
|
||||
Auctions by Country
|
||||
</h3>
|
||||
<div id="country-chart" style="height: 300px;"></div>
|
||||
</div>
|
||||
|
||||
<!-- Category Distribution -->
|
||||
<div class="bg-white rounded-lg shadow-md p-6">
|
||||
<h3 class="text-xl font-semibold mb-4 text-gray-800">
|
||||
<i class="fas fa-tags mr-2 text-green-600"></i>
|
||||
Lots by Category
|
||||
</h3>
|
||||
<div id="category-chart" style="height: 300px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Closing Soon Table -->
|
||||
<div class="bg-white rounded-lg shadow-md p-6 mb-8">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h3 class="text-xl font-semibold text-gray-800">
|
||||
<i class="fas fa-exclamation-triangle mr-2 text-red-600"></i>
|
||||
Lots Closing Soon (< 30 min)
|
||||
</h3>
|
||||
<button onclick="fetchClosingSoon()" class="bg-red-600 text-white px-4 py-2 rounded-lg hover:bg-red-700 transition-colors">
|
||||
<i class="fas fa-sync mr-2"></i>Refresh
|
||||
</button>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Lot ID</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Title</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Current Bid</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Closing Time</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Minutes Left</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="closing-soon-table" class="bg-white divide-y divide-gray-200">
|
||||
<tr>
|
||||
<td colspan="6" class="px-6 py-4 text-center text-gray-500">Loading...</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recent Activity Log -->
|
||||
<div class="bg-white rounded-lg shadow-md p-6">
|
||||
<h3 class="text-xl font-semibold mb-4 text-gray-800">
|
||||
<i class="fas fa-history mr-2 text-purple-600"></i>
|
||||
Activity Log
|
||||
</h3>
|
||||
<div id="activity-log" class="space-y-2 max-h-64 overflow-y-auto">
|
||||
<div class="text-sm text-gray-500">Monitoring system started...</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="bg-gray-800 text-white py-6 mt-12">
|
||||
<div class="container mx-auto px-4 text-center">
|
||||
<p class="text-gray-300">
|
||||
<i class="fas fa-info-circle mr-2"></i>
|
||||
Auctiora - Auction Data Processing & Monitoring System
|
||||
</p>
|
||||
<p class="text-sm text-gray-400 mt-2">
|
||||
Architecture: Quarkus + SQLite + REST API | Auto-refresh: 15 seconds
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
// Dashboard state
|
||||
let dashboardData = {
|
||||
status: {},
|
||||
statistics: {},
|
||||
rateLimitStats: {},
|
||||
closingSoon: []
|
||||
};
|
||||
|
||||
// Initialize dashboard
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
addLog('Dashboard initialized');
|
||||
fetchAllData();
|
||||
|
||||
// Auto-refresh every 15 seconds
|
||||
setInterval(fetchAllData, 15000);
|
||||
});
|
||||
|
||||
// Fetch all dashboard data
|
||||
async function fetchAllData() {
|
||||
try {
|
||||
await Promise.all([
|
||||
fetchStatus(),
|
||||
fetchStatistics(),
|
||||
fetchRateLimitStats(),
|
||||
fetchClosingSoon()
|
||||
]);
|
||||
updateLastUpdate();
|
||||
addLog('Dashboard data refreshed');
|
||||
} catch (error) {
|
||||
console.error('Error fetching dashboard data:', error);
|
||||
addLog('Error: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch system status
|
||||
async function fetchStatus() {
|
||||
try {
|
||||
const response = await fetch('/api/monitor/status');
|
||||
if (!response.ok) throw new Error('Failed to fetch status');
|
||||
|
||||
dashboardData.status = await response.json();
|
||||
|
||||
document.getElementById('total-auctions').textContent = dashboardData.status.auctions || 0;
|
||||
document.getElementById('total-lots').textContent = dashboardData.status.lots || 0;
|
||||
document.getElementById('total-images').textContent = dashboardData.status.images || 0;
|
||||
document.getElementById('closing-soon').textContent = dashboardData.status.closingSoon || 0;
|
||||
} catch (error) {
|
||||
console.error('Error fetching status:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch statistics
|
||||
async function fetchStatistics() {
|
||||
try {
|
||||
const response = await fetch('/api/monitor/statistics');
|
||||
if (!response.ok) throw new Error('Failed to fetch statistics');
|
||||
|
||||
dashboardData.statistics = await response.json();
|
||||
|
||||
document.getElementById('active-lots').textContent = dashboardData.statistics.activeLots || 0;
|
||||
document.getElementById('lots-with-bids').textContent = dashboardData.statistics.lotsWithBids || 0;
|
||||
document.getElementById('total-bid-value').textContent = dashboardData.statistics.totalBidValue || '€0.00';
|
||||
document.getElementById('average-bid').textContent = dashboardData.statistics.averageBid || '€0.00';
|
||||
} catch (error) {
|
||||
console.error('Error fetching statistics:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch rate limit stats
|
||||
async function fetchRateLimitStats() {
|
||||
try {
|
||||
const response = await fetch('/api/monitor/rate-limit/stats');
|
||||
if (!response.ok) throw new Error('Failed to fetch rate limit stats');
|
||||
|
||||
dashboardData.rateLimitStats = await response.json();
|
||||
|
||||
// Aggregate stats from all hosts
|
||||
let totalRequests = 0, successfulRequests = 0, failedRequests = 0, avgDuration = 0;
|
||||
const stats = dashboardData.rateLimitStats.statistics || {};
|
||||
const hostCount = Object.keys(stats).length;
|
||||
|
||||
for (const hostStats of Object.values(stats)) {
|
||||
totalRequests += hostStats.totalRequests || 0;
|
||||
successfulRequests += hostStats.successfulRequests || 0;
|
||||
failedRequests += hostStats.failedRequests || 0;
|
||||
avgDuration += hostStats.averageDurationMs || 0;
|
||||
}
|
||||
|
||||
document.getElementById('rate-total-requests').textContent = totalRequests;
|
||||
document.getElementById('rate-successful').textContent = successfulRequests;
|
||||
document.getElementById('rate-failed').textContent = failedRequests;
|
||||
document.getElementById('rate-avg-duration').textContent =
|
||||
hostCount > 0 ? (avgDuration / hostCount).toFixed(0) + ' ms' : '--';
|
||||
} catch (error) {
|
||||
console.error('Error fetching rate limit stats:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch closing soon lots
|
||||
async function fetchClosingSoon() {
|
||||
try {
|
||||
const response = await fetch('/api/monitor/lots/closing-soon?minutes=30');
|
||||
if (!response.ok) throw new Error('Failed to fetch closing soon lots');
|
||||
|
||||
dashboardData.closingSoon = await response.json();
|
||||
updateClosingSoonTable();
|
||||
|
||||
// Update charts (placeholder for now)
|
||||
updateCharts();
|
||||
} catch (error) {
|
||||
console.error('Error fetching closing soon:', error);
|
||||
document.getElementById('closing-soon-table').innerHTML =
|
||||
'<tr><td colspan="6" class="px-6 py-4 text-center text-red-600">Error loading data</td></tr>';
|
||||
}
|
||||
}
|
||||
|
||||
// Update closing soon table
|
||||
function updateClosingSoonTable() {
|
||||
const tableBody = document.getElementById('closing-soon-table');
|
||||
|
||||
if (dashboardData.closingSoon.length === 0) {
|
||||
tableBody.innerHTML = '<tr><td colspan="6" class="px-6 py-4 text-center text-gray-500">No lots closing soon</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
tableBody.innerHTML = '';
|
||||
dashboardData.closingSoon.forEach(lot => {
|
||||
const row = document.createElement('tr');
|
||||
row.className = 'hover:bg-gray-50';
|
||||
|
||||
const minutesLeft = lot.minutesUntilClose || 0;
|
||||
const badgeColor = minutesLeft < 10 ? 'bg-red-100 text-red-800' : 'bg-yellow-100 text-yellow-800';
|
||||
|
||||
row.innerHTML = `
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">${lot.lotId || '--'}</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-900">${lot.title || 'N/A'}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-bold text-green-600">${lot.currency || ''}${lot.currentBid ? lot.currentBid.toFixed(2) : '0.00'}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${lot.closingTime || 'N/A'}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${badgeColor}">
|
||||
${minutesLeft} min
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
|
||||
<a href="${lot.url || '#'}" target="_blank" class="text-blue-600 hover:text-blue-900">
|
||||
<i class="fas fa-external-link-alt"></i>
|
||||
</a>
|
||||
</td>
|
||||
`;
|
||||
|
||||
tableBody.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
// Update charts (placeholder)
|
||||
function updateCharts() {
|
||||
// Country distribution pie chart
|
||||
const countryData = {
|
||||
values: [5, 3, 2],
|
||||
labels: ['NL', 'BE', 'DE'],
|
||||
type: 'pie',
|
||||
marker: { colors: ['#3b82f6', '#10b981', '#f59e0b'] }
|
||||
};
|
||||
|
||||
Plotly.newPlot('country-chart', [countryData], {
|
||||
showlegend: true,
|
||||
margin: { t: 20, b: 20, l: 20, r: 20 }
|
||||
});
|
||||
|
||||
// Category distribution
|
||||
const categoryData = {
|
||||
values: [4, 3, 2, 1],
|
||||
labels: ['Machinery', 'Material Handling', 'Power Generation', 'Furniture'],
|
||||
type: 'pie',
|
||||
marker: { colors: ['#3b82f6', '#10b981', '#f59e0b', '#ef4444'] }
|
||||
};
|
||||
|
||||
Plotly.newPlot('category-chart', [categoryData], {
|
||||
showlegend: true,
|
||||
margin: { t: 20, b: 20, l: 20, r: 20 }
|
||||
});
|
||||
}
|
||||
|
||||
// Trigger workflow
|
||||
async function triggerWorkflow(workflow) {
|
||||
const statusId = 'status-' + workflow.split('-')[0];
|
||||
const statusEl = document.getElementById(statusId);
|
||||
|
||||
statusEl.textContent = 'Running...';
|
||||
statusEl.className = 'text-xs text-blue-600 font-semibold';
|
||||
|
||||
addLog(`Triggering workflow: ${workflow}`);
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/monitor/trigger/${workflow}`, {
|
||||
method: 'POST'
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error('Workflow trigger failed');
|
||||
|
||||
const result = await response.json();
|
||||
addLog(`✓ ${result.message || 'Workflow completed'}`, 'success');
|
||||
statusEl.textContent = 'Complete';
|
||||
statusEl.className = 'text-xs text-green-600 font-semibold';
|
||||
|
||||
// Refresh data after workflow
|
||||
setTimeout(fetchAllData, 2000);
|
||||
} catch (error) {
|
||||
console.error('Error triggering workflow:', error);
|
||||
addLog(`✗ Workflow failed: ${error.message}`, 'error');
|
||||
statusEl.textContent = 'Failed';
|
||||
statusEl.className = 'text-xs text-red-600 font-semibold';
|
||||
}
|
||||
|
||||
// Reset status after 5 seconds
|
||||
setTimeout(() => {
|
||||
statusEl.textContent = 'Ready';
|
||||
statusEl.className = 'text-xs text-gray-500';
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
// Add log entry
|
||||
function addLog(message, type = 'info') {
|
||||
const logContainer = document.getElementById('activity-log');
|
||||
const logEntry = document.createElement('div');
|
||||
const timestamp = new Date().toLocaleTimeString();
|
||||
|
||||
let iconClass = 'fas fa-info-circle text-blue-600';
|
||||
let textClass = 'text-gray-700';
|
||||
|
||||
if (type === 'success') {
|
||||
iconClass = 'fas fa-check-circle text-green-600';
|
||||
textClass = 'text-green-700';
|
||||
} else if (type === 'error') {
|
||||
iconClass = 'fas fa-exclamation-circle text-red-600';
|
||||
textClass = 'text-red-700';
|
||||
}
|
||||
|
||||
logEntry.className = `text-sm flex items-start space-x-2 ${textClass}`;
|
||||
logEntry.innerHTML = `
|
||||
<i class="${iconClass} mt-1"></i>
|
||||
<span class="text-gray-500">${timestamp}</span>
|
||||
<span>${message}</span>
|
||||
`;
|
||||
|
||||
logContainer.insertBefore(logEntry, logContainer.firstChild);
|
||||
|
||||
// Keep only last 20 entries
|
||||
while (logContainer.children.length > 20) {
|
||||
logContainer.removeChild(logContainer.lastChild);
|
||||
}
|
||||
}
|
||||
|
||||
// Update last update timestamp
|
||||
function updateLastUpdate() {
|
||||
const now = new Date();
|
||||
document.getElementById('last-update').textContent =
|
||||
`Last updated: ${now.toLocaleTimeString()}`;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,30 +0,0 @@
|
||||
{
|
||||
"total_kavels": 5,
|
||||
"categories": {
|
||||
"Machinery": 1,
|
||||
"Material Handling": 1,
|
||||
"Furniture": 1,
|
||||
"Power Generation": 1,
|
||||
"Laboratory": 1
|
||||
},
|
||||
"locations": {
|
||||
"Amsterdam, Netherlands": 1,
|
||||
"Rotterdam, Netherlands": 1,
|
||||
"Utrecht, Netherlands": 1,
|
||||
"Eindhoven, Netherlands": 1,
|
||||
"Leiden, Netherlands": 1
|
||||
},
|
||||
"price_ranges": {
|
||||
"\u20ac5,000 - \u20ac15,000": 2,
|
||||
"Under \u20ac5,000": 1,
|
||||
"\u20ac15,000 - \u20ac25,000": 12,
|
||||
"Over \u20ac25,000": 1
|
||||
},
|
||||
"bid_activity": {
|
||||
"Medium (10-24 bids)": 12,
|
||||
"Low (1-9 bids)": 1,
|
||||
"High (25-39 bids)": 12,
|
||||
"Very High (40+ bids)": 1
|
||||
},
|
||||
"time_distribution": {}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
id,title,description,current_bid,bid_count,end_date,location,auction_place,category,condition,year,images,specifications,url
|
||||
KAVEL_001,Industrial CNC Machine - Haas VF-2,"Used Haas VF-2 vertical machining center, 30 taper, 10,000 RPM spindle","€12,500",23,2025-11-28 14:00:00,"Amsterdam, Netherlands",Metalworking Equipment Auction,Machinery,Used,2018,"https://example.com/image1.jpg, https://example.com/image2.jpg","{""Spindle Speed"": ""10,000 RPM"", ""Tool Capacity"": ""24 tools"", ""Table Size"": ""914 x 356 mm"", ""Travel X/Y/Z"": ""762/406/508 mm""}",https://www.troostwijkauctions.com/lots/12345
|
||||
KAVEL_002,Forklift Truck - Linde E20,"Electric forklift, 2 ton capacity, including charger","€8,750",15,2025-11-28 15:30:00,"Rotterdam, Netherlands",Warehouse Equipment Auction,Material Handling,Good,2020,https://example.com/forklift1.jpg,"{""Capacity"": ""2000 kg"", ""Lift Height"": ""4.5 meters"", ""Battery"": ""80V lithium-ion"", ""Hours"": ""1,250 hours""}",https://www.troostwijkauctions.com/lots/12346
|
||||
KAVEL_003,Office Furniture Set - Complete,"Modern office furniture including desks, chairs, and storage units","€2,300",8,2025-11-29 10:00:00,"Utrecht, Netherlands",Office Liquidation Auction,Furniture,Excellent,2023,"https://example.com/office1.jpg, https://example.com/office2.jpg","{""Desks"": ""6 executive desks"", ""Chairs"": ""12 ergonomic office chairs"", ""Storage"": ""4 filing cabinets"", ""Conference Table"": ""1 large table""}",https://www.troostwijkauctions.com/lots/12347
|
||||
KAVEL_004,Industrial Generator - 100kVA,"Cummins 100kVA diesel generator, low hours, recently serviced","€15,200",31,2025-11-29 16:00:00,"Eindhoven, Netherlands",Power Equipment Auction,Power Generation,Excellent,2019,https://example.com/generator1.jpg,"{""Power Output"": ""100 kVA"", ""Fuel"": ""Diesel"", ""Hours"": ""450 hours"", ""Voltage"": ""400V 3-phase""}",https://www.troostwijkauctions.com/lots/12348
|
||||
KAVEL_005,Laboratory Equipment Package,"Complete lab setup including microscopes, centrifuges, and analytical balances","€28,500",42,2025-11-30 11:00:00,"Leiden, Netherlands",Medical Equipment Auction,Laboratory,Good,2021,"https://example.com/lab1.jpg, https://example.com/lab2.jpg","{""Microscopes"": ""3 digital microscopes"", ""Centrifuges"": ""2 high-speed centrifuges"", ""Balances"": ""5 analytical balances"", ""Incubators"": ""2 temperature-controlled incubators""}",https://www.troostwijkauctions.com/lots/12349
|
||||
|
@@ -1,120 +0,0 @@
|
||||
[
|
||||
{
|
||||
"id": "KAVEL_001",
|
||||
"title": "Industrial CNC Machine - Haas VF-2",
|
||||
"description": "Used Haas VF-2 vertical machining center, 30 taper, 10,000 RPM spindle",
|
||||
"current_bid": "€12,500",
|
||||
"bid_count": "23",
|
||||
"end_date": "2025-11-28 14:00:00",
|
||||
"location": "Amsterdam, Netherlands",
|
||||
"auction_place": "Metalworking Equipment Auction",
|
||||
"category": "Machinery",
|
||||
"condition": "Used",
|
||||
"year": "2018",
|
||||
"images": [
|
||||
"https://example.com/image1.jpg",
|
||||
"https://example.com/image2.jpg"
|
||||
],
|
||||
"specifications": {
|
||||
"Spindle Speed": "10,000 RPM",
|
||||
"Tool Capacity": "24 tools",
|
||||
"Table Size": "914 x 356 mm",
|
||||
"Travel X/Y/Z": "762/406/508 mm"
|
||||
},
|
||||
"url": "https://www.troostwijkauctions.com/lots/12345"
|
||||
},
|
||||
{
|
||||
"id": "KAVEL_002",
|
||||
"title": "Forklift Truck - Linde E20",
|
||||
"description": "Electric forklift, 2 ton capacity, including charger",
|
||||
"current_bid": "€8,750",
|
||||
"bid_count": "15",
|
||||
"end_date": "2025-11-28 15:30:00",
|
||||
"location": "Rotterdam, Netherlands",
|
||||
"auction_place": "Warehouse Equipment Auction",
|
||||
"category": "Material Handling",
|
||||
"condition": "Good",
|
||||
"year": "2020",
|
||||
"images": [
|
||||
"https://example.com/forklift1.jpg"
|
||||
],
|
||||
"specifications": {
|
||||
"Capacity": "2000 kg",
|
||||
"Lift Height": "4.5 meters",
|
||||
"Battery": "80V lithium-ion",
|
||||
"Hours": "1,250 hours"
|
||||
},
|
||||
"url": "https://www.troostwijkauctions.com/lots/12346"
|
||||
},
|
||||
{
|
||||
"id": "KAVEL_003",
|
||||
"title": "Office Furniture Set - Complete",
|
||||
"description": "Modern office furniture including desks, chairs, and storage units",
|
||||
"current_bid": "€2,300",
|
||||
"bid_count": "8",
|
||||
"end_date": "2025-11-29 10:00:00",
|
||||
"location": "Utrecht, Netherlands",
|
||||
"auction_place": "Office Liquidation Auction",
|
||||
"category": "Furniture",
|
||||
"condition": "Excellent",
|
||||
"year": "2023",
|
||||
"images": [
|
||||
"https://example.com/office1.jpg",
|
||||
"https://example.com/office2.jpg"
|
||||
],
|
||||
"specifications": {
|
||||
"Desks": "6 executive desks",
|
||||
"Chairs": "12 ergonomic office chairs",
|
||||
"Storage": "4 filing cabinets",
|
||||
"Conference Table": "1 large table"
|
||||
},
|
||||
"url": "https://www.troostwijkauctions.com/lots/12347"
|
||||
},
|
||||
{
|
||||
"id": "KAVEL_004",
|
||||
"title": "Industrial Generator - 100kVA",
|
||||
"description": "Cummins 100kVA diesel generator, low hours, recently serviced",
|
||||
"current_bid": "€15,200",
|
||||
"bid_count": "31",
|
||||
"end_date": "2025-11-29 16:00:00",
|
||||
"location": "Eindhoven, Netherlands",
|
||||
"auction_place": "Power Equipment Auction",
|
||||
"category": "Power Generation",
|
||||
"condition": "Excellent",
|
||||
"year": "2019",
|
||||
"images": [
|
||||
"https://example.com/generator1.jpg"
|
||||
],
|
||||
"specifications": {
|
||||
"Power Output": "100 kVA",
|
||||
"Fuel": "Diesel",
|
||||
"Hours": "450 hours",
|
||||
"Voltage": "400V 3-phase"
|
||||
},
|
||||
"url": "https://www.troostwijkauctions.com/lots/12348"
|
||||
},
|
||||
{
|
||||
"id": "KAVEL_005",
|
||||
"title": "Laboratory Equipment Package",
|
||||
"description": "Complete lab setup including microscopes, centrifuges, and analytical balances",
|
||||
"current_bid": "€28,500",
|
||||
"bid_count": "42",
|
||||
"end_date": "2025-11-30 11:00:00",
|
||||
"location": "Leiden, Netherlands",
|
||||
"auction_place": "Medical Equipment Auction",
|
||||
"category": "Laboratory",
|
||||
"condition": "Good",
|
||||
"year": "2021",
|
||||
"images": [
|
||||
"https://example.com/lab1.jpg",
|
||||
"https://example.com/lab2.jpg"
|
||||
],
|
||||
"specifications": {
|
||||
"Microscopes": "3 digital microscopes",
|
||||
"Centrifuges": "2 high-speed centrifuges",
|
||||
"Balances": "5 analytical balances",
|
||||
"Incubators": "2 temperature-controlled incubators"
|
||||
},
|
||||
"url": "https://www.troostwijkauctions.com/lots/12349"
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user