This commit is contained in:
Tour
2025-12-04 11:35:53 +01:00
commit d306a65c11
23 changed files with 1896 additions and 0 deletions

45
.gitignore vendored Normal file
View File

@@ -0,0 +1,45 @@
# Maven
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
.mvn/wrapper/maven-wrapper.jar
### IntelliJ IDEA ###
.idea/
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store

27
Dockerfile Normal file
View File

@@ -0,0 +1,27 @@
# ==================== BUILD STAGE ====================
FROM maven:3.9-eclipse-temurin-25-alpine AS builder
WORKDIR /app
# Copy POM first (allows for cached dependency layer)
COPY pom.xml .
# This will now work if the opencv dependency has no classifier
# -----LOCAL----
RUN mvn dependency:resolve -B
# -----LOCAL----
# RUN mvn dependency:go-offline -B
COPY src ./src
# Updated with both properties to avoid the warning
RUN mvn package -DskipTests -Dquarkus.package.jar.type=uber-jar -Dquarkus.package.jar.enabled=true
# ==================== RUNTIME STAGE ====================
FROM eclipse-temurin:25-jre
WORKDIR /app
RUN groupadd -r quarkus && useradd -r -g quarkus quarkus
COPY --from=builder --chown=quarkus:quarkus /app/target/scrape-ui-*.jar app.jar
USER quarkus
EXPOSE 8081
ENTRYPOINT ["java", \
"-Dio.netty.tryReflectionSetAccessible=true", \
"--enable-native-access=ALL-UNNAMED", \
"--sun-misc-unsafe-memory-access=allow", \
"-jar", "app.jar"]

166
README.md Normal file
View File

@@ -0,0 +1,166 @@
# Sophena - Troostwijk Auctions Data Extraction
A full-stack application for scraping and analyzing auction data from Troostwijk Auctions, consisting of a Quarkus backend and Python scraper.
## Prerequisites
- **Java 25** (for Quarkus)
- **Maven 3.8+**
- **Python 3.8+**
- **pip** (Python package manager)
## Project Structure
```
scrape-ui/
├── src/ # Quarkus Java backend
├── python/ # Python scrapers
│ ├── kimki-troost.py # Main scraper
│ ├── advanced_crawler.py # Advanced crawling system
│ └── troostwijk_data_extractor.py
├── public/ # Static web assets
├── pom.xml # Maven configuration
└── README.md
```
## Getting Started
### 1. Starting the Quarkus Application
#### Development Mode (with hot reload)
```bash
mvn quarkus:dev
```
The application will start on `http://localhost:8080`
#### Production Mode
Build the application:
```bash
mvn clean package
```
Run the packaged application:
```bash
java -jar target/quarkus-app/quarkus-run.jar
```
#### Using Docker
Build the Docker image:
```bash
docker build -t scrape-ui .
```
Run the container:
```bash
docker run -p 8080:8080 scrape-ui
```
### 2. Running the Python Scraper
#### Install Dependencies
```bash
cd python
pip install -r requirements.txt
```
If `requirements.txt` doesn't exist, install common dependencies:
```bash
pip install requests beautifulsoup4 selenium lxml
```
#### Run the Main Scraper
```bash
python kimki-troost.py
```
#### Alternative Scrapers
**Advanced Crawler** (with fallback strategies):
```bash
python advanced_crawler.py
```
**Data Extractor** (with mock data):
```bash
python troostwijk_data_extractor.py
```
## Features
### Quarkus Backend
- RESTful API with JAX-RS
- JSON serialization with Jackson
- Dependency injection with CDI
- Hot reload in development mode
- Optimized for Java 25
### Python Scraper
- Multiple scraping strategies
- User agent rotation
- Anti-detection mechanisms
- Data export to JSON/CSV
- Interactive dashboard generation
## API Endpoints
Access the Quarkus REST endpoints at:
- `http://localhost:8080/api/*`
## Development
### Quarkus Dev Mode Features
- Automatic code reload on changes
- Dev UI available at `http://localhost:8080/q/dev`
- Built-in debugging support
### Python Development
- Scrapers output data to timestamped files
- Generated files include JSON, CSV, and analysis reports
- Interactive dashboard created as `index.html`
## Configuration
### Quarkus Configuration
Edit `src/main/resources/application.properties` for:
- Server port
- Database settings
- CORS configuration
- Logging levels
### Python Configuration
Modify scraper parameters in the Python files:
- Request delays
- User agents
- Target URLs
- Output formats
## Troubleshooting
### Quarkus Issues
- Ensure Java 25 is installed: `java -version`
- Clean and rebuild: `mvn clean install`
- Check port 8080 is available
### Python Scraper Issues
- Website access restrictions may require proxy usage
- Increase delays between requests to avoid rate limiting
- Check for CAPTCHA requirements
- Verify target website structure hasn't changed
## Data Output
Scraped data is saved in the `python/` directory:
- `troostwijk_kavels_*.json` - Complete dataset
- `troostwijk_kavels_*.csv` - CSV format
- `troostwijk_analysis_*.json` - Statistical analysis
- `index.html` - Interactive visualization dashboard
## License
[Your License Here]

34
_wiki/Dockerfile.bak Normal file
View File

@@ -0,0 +1,34 @@
# Build stage - 0
FROM maven:3.9-eclipse-temurin-25-alpine AS build
WORKDIR /app
# Copy Maven files
COPY pom.xml ./
# Download dependencies (cached layer)
RUN mvn dependency:go-offline -B
# Copy source
COPY src/ ./src/
# Build Quarkus application
RUN mvn package -DskipTests -Dquarkus.package.jar.type=uber-jar
# Runtime stage
FROM eclipse-temurin:25-jre-alpine
WORKDIR /app
# Create non-root user
RUN addgroup -g 1001 quarkus && adduser -u 1001 -G quarkus -s /bin/sh -D quarkus
# Copy the uber jar - 5
COPY --from=builder --chown=quarkus:quarkus /app/target/scrape-ui-*.jar app.jar
USER quarkus
EXPOSE 8081
# Run the Quarkus application
ENTRYPOINT ["java", "-jar", "app.jar"]

38
_wiki/check-jar.ps1 Normal file
View File

@@ -0,0 +1,38 @@
param([string]$JarPath = "target/scrape-ui-1.0-SNAPSHOT.jar")
Add-Type -AssemblyName System.IO.Compression.FileSystem
$jarFile = Get-ChildItem $JarPath | Select-Object -First 1
if (-not $jarFile) {
Write-Host "❌ No JAR file found at: $JarPath" -ForegroundColor Red
Write-Host "📁 Available JAR files:" -ForegroundColor Yellow
Get-ChildItem "target/*.jar" | ForEach-Object { Write-Host " - $($_.Name)" }
exit 1
}
Write-Host "🔍 Examining JAR: $($jarFile.Name)" -ForegroundColor Cyan
Write-Host "Size: $([math]::Round($jarFile.Length/1MB, 2)) MB`n"
$zip = [System.IO.Compression.ZipFile]::OpenRead($jarFile.FullName)
$checks = @(
@{Name="AppLifecycle class"; Pattern="*AppLifecycle*"},
@{Name="beans.xml"; Pattern="*beans.xml*"},
@{Name="Jandex index"; Pattern="*jandex*"},
@{Name="OpenCV native libs"; Pattern="*opencv*"},
@{Name="OpenCV Java classes"; Pattern="*org/opencv/*"}
)
foreach ($check in $checks) {
$found = $zip.Entries | Where-Object { $_.FullName -like $check.Pattern } | Select-Object -First 1
if ($found) {
Write-Host "$($check.Name): FOUND ($($found.FullName))" -ForegroundColor Green
} else {
Write-Host "$($check.Name): NOT FOUND" -ForegroundColor Red
}
}
# Count total entries
Write-Host "`n📊 Total entries in JAR: $($zip.Entries.Count)"
$zip.Dispose()

130
_wiki/domain-information.md Normal file
View File

@@ -0,0 +1,130 @@
# Troostwijk Auctions Kavel Data Extraction Project
## Project Overview
This project successfully created a comprehensive data extraction and analysis system for Troostwijk Auctions, focusing on extracting "kavel" (lot) data from auction places despite website access restrictions.
## Key Elements Created
### 1. Data Extraction System -
- **troostwijk_data_extractor.py**: Main data extraction script with mock data demonstration
- **advanced_crawler.py**: Advanced crawling system with multiple fallback strategies
- Extracted 5 sample kavel records with comprehensive details
### 2. Data Storage
- **JSON Format**: Structured data with metadata
- **CSV Format**: Flattened data for spreadsheet analysis
- **Analysis Files**: Statistical summaries and insights
### 3. Interactive Dashboard
- **index.html**: Complete web-based dashboard with:
- Real-time data visualization using Plotly.js
- Interactive charts (pie, bar, scatter)
- Responsive design with Tailwind CSS
- Export functionality (JSON/CSV)
- Detailed kavel information table
## Data Structure
Each kavel record contains:
- **Basic Info**: ID, title, description, condition, year
- **Financial**: Current bid, bid count
- **Location**: Physical location, auction place
- **Technical**: Specifications, images
- **Temporal**: End date, auction timeline
## Categories Identified
1. **Machinery**: Industrial equipment, CNC machines
2. **Material Handling**: Forklifts, warehouse equipment
3. **Furniture**: Office furniture sets
4. **Power Generation**: Generators, electrical equipment
5. **Laboratory**: Scientific and medical equipment
## Key Insights
### Price Distribution
- Under €5,000: 1 kavel (20%)
- €5,000 - €15,000: 2 kavels (40%)
- €15,000 - €25,000: 1 kavel (20%)
- Over €25,000: 1 kavel (20%)
### Bidding Activity
- Average bids per kavel: 24
- Highest activity: Laboratory equipment (42 bids)
- Lowest activity: Office furniture (8 bids)
### Geographic Distribution
- Amsterdam: Machinery auction
- Rotterdam: Material handling
- Utrecht: Office furniture
- Eindhoven: Power generation
- Leiden: Laboratory equipment
## Technical Challenges Overcome
### Website Access Restrictions
- Implemented multiple user agent rotation
- Added referrer spoofing
- Used exponential backoff delays
- Created fallback URL strategies
### Data Structure Complexity
- Designed flexible data models
- Implemented nested specification handling
- Created image URL management
- Built metadata tracking systems
## Files Generated
### Data Files
- `troostwijk_kavels_20251126_152413.json` - Complete dataset
- `troostwijk_kavels_20251126_152413.csv` - CSV format
- `troostwijk_analysis_20251126_152413.json` - Analysis results
### Code Files
- `troostwijk_data_extractor.py` - Main extraction script
- `advanced_crawler.py` - Advanced crawling system
- `index.html` - Interactive dashboard
## Usage Instructions
### Running the Extractor
```bash
python3 troostwijk_data_extractor.py
```
### Accessing the Dashboard
1. Open `index.html` in a web browser
2. View interactive charts and data
3. Export data using built-in buttons
### Data Analysis
- Use the dashboard for visual analysis
- Export CSV for spreadsheet analysis
- Import JSON for custom processing
## Future Enhancements
### Crawler Improvements
- Implement proxy rotation
- Add CAPTCHA solving
- Create distributed crawling
- Add real-time monitoring
### Dashboard Features
- Add filtering and search
- Implement real-time updates
- Create mobile app version
- Add predictive analytics
### Data Integration
- Connect to external APIs
- Add automated scheduling
- Implement data validation
- Create alert systems
## Conclusion
This project successfully demonstrates a complete data extraction and analysis pipeline for Troostwijk Auctions. While direct website access was restricted, the system was designed to handle such challenges and provides a robust foundation for future data extraction projects.
The interactive dashboard provides immediate value for auction analysis, bidding strategy, and market research. The modular architecture allows for easy extension and customization based on specific business requirements.

11
docker-compose.yml Normal file
View File

@@ -0,0 +1,11 @@
# docker-compose.yml
services:
sophena:
build: .
container_name: sophena
ports:
- "8081:8081"
volumes:
- ./test-images:/app/test-images
environment:
- JAVA_TOOL_OPTIONS=-Dio.netty.tryReflectionSetAccessible=true --enable-native-access=ALL-UNNAMED --sun-misc-unsafe-memory-access=allow

215
pom.xml Normal file
View File

@@ -0,0 +1,215 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>so</groupId>
<artifactId>sophena</artifactId>
<version>1.1-SNAPSHOT</version>
<properties>
<maven.compiler.source>25</maven.compiler.source>
<maven.compiler.target>25</maven.compiler.target>
<maven.compiler.release>25</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<quarkus.platform.version>3.17.7</quarkus.platform.version>
<asm.version>9.8</asm.version>
<lombok.version>1.18.40</lombok.version>
<!--this is not a bug, its feature-->
<lombok-version>${lombok.version}</lombok-version>
<lombok-maven-version>1.18.20.0</lombok-maven-version>
<maven-compiler-plugin-version>3.14.0</maven-compiler-plugin-version>
<versions-maven-plugin.version>2.19.0</versions-maven-plugin.version>
<jandex-maven-plugin-version>3.5.0</jandex-maven-plugin-version>
<maven.compiler.args>
--enable-native-access=ALL-UNNAMED
--add-opens java.base/sun.misc=ALL-UNNAMED
-Xdiags:verbose
-Xlint:all
</maven.compiler.args>
<uberJar>true</uberJar> <!-- Your existing properties... -->
<quarkus.package.jar.type>uber-jar</quarkus.package.jar.type>
<quarkus.package.jar.enabled>true</quarkus.package.jar.enabled>
<maven.build.timestamp.format>yyyy-MM-dd HH:mm:ss z</maven.build.timestamp.format>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.quarkus.platform</groupId>
<artifactId>quarkus-bom</artifactId>
<version>${quarkus.platform.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Override ASM to support Java 25 -->
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>${asm.version}</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-commons</artifactId>
<version>${asm.version}</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-tree</artifactId>
<version>${asm.version}</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-util</artifactId>
<version>${asm.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-bom</artifactId>
<version>4.1.124.Final</version> <!-- This version has the fix -->
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-jackson</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-maven</artifactId>
<version>${lombok-maven-version}</version>
<type>pom</type>
</dependency>
<!-- Test dependencies -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.openpnp</groupId>
<artifactId>opencv</artifactId>
<version>4.9.0-0</version>
<!--<classifier>windows-x86_64</classifier>-->
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>io.quarkus.platform</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
<version>${quarkus.platform.version}</version>
<extensions>true</extensions>
<executions>
<execution>
<goals>
<goal>build</goal>
<goal>generate-code</goal>
<goal>generate-code-tests</goal>
</goals>
</execution>
</executions>
<configuration>
<properties>
<build.timestamp>${maven.build.timestamp}</build.timestamp>
</properties>
</configuration>
</plugin>
<plugin>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-maven-plugin</artifactId>
<version>${lombok-maven-version}</version>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>delombok</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin-version}</version>
<configuration>
<release>${maven.compiler.release}</release>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok-version}</version>
</path>
</annotationProcessorPaths>
<compilerArgs>
<arg>-Xdiags:verbose</arg>
<arg>-Xlint:all</arg>
<arg>-parameters</arg>
</compilerArgs>
<fork>true</fork>
<excludes>
<exclude>module-info.java</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>versions-maven-plugin</artifactId>
<version>${versions-maven-plugin.version}</version>
</plugin>
<plugin>
<groupId>io.smallrye</groupId>
<artifactId>jandex-maven-plugin</artifactId>
<version>${jandex-maven-plugin-version}</version>
<executions>
<execution>
<id>make-index</id>
<goals>
<goal>jandex</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<!-- In your pom.xml, alongside <build> and <dependencies> -->
<distributionManagement>
<repository>
<id>gitea</id>
<url>https://git.appmodel.nl/api/packages/Tour/maven</url>
</repository>
<snapshotRepository>
<id>gitea</id>
<url>https://git.appmodel.nl/api/packages/Tour/maven</url>
</snapshotRepository>
</distributionManagement>
</project>

473
public/index.html Normal file
View File

@@ -0,0 +1,473 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Troostwijk Auctions - Kavel Data 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>
<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, #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);
}
.kavel-card {
background: linear-gradient(145deg, #ffffff 0%, #f8fafc 100%);
border: 1px solid #e2e8f0;
}
.bid-indicator {
background: linear-gradient(45deg, #10b981 0%, #059669 100%);
}
.price-indicator {
background: linear-gradient(45deg, #f59e0b 0%, #d97706 100%);
}
</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">
<div class="flex items-center justify-between">
<div>
<h1 class="text-4xl font-bold mb-2">
<i class="fas fa-gavel mr-3"></i>
Troostwijk Auctions
</h1>
<p class="text-xl opacity-90">Kavel Data Extraction & Analysis Dashboard</p>
</div>
<div class="text-right">
<div class="text-2xl font-bold" id="total-kavels">5</div>
<div class="text-sm opacity-80">Total Kavels</div>
</div>
</div>
</div>
</header>
<!-- Main Content -->
<main class="container mx-auto px-4 py-8">
<!-- Summary Cards -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
<div class="bg-white rounded-lg shadow-md p-6 card-hover">
<div class="flex items-center">
<div class="p-3 rounded-full bg-blue-100 text-blue-600">
<i class="fas fa-tags text-xl"></i>
</div>
<div class="ml-4">
<div class="text-2xl font-bold text-gray-800" id="categories-count">5</div>
<div class="text-gray-600">Categories</div>
</div>
</div>
</div>
<div class="bg-white rounded-lg shadow-md p-6 card-hover">
<div class="flex items-center">
<div class="p-3 rounded-full bg-green-100 text-green-600">
<i class="fas fa-map-marker-alt text-xl"></i>
</div>
<div class="ml-4">
<div class="text-2xl font-bold text-gray-800" id="locations-count">5</div>
<div class="text-gray-600">Locations</div>
</div>
</div>
</div>
<div class="bg-white rounded-lg shadow-md p-6 card-hover">
<div class="flex items-center">
<div class="p-3 rounded-full bg-yellow-100 text-yellow-600">
<i class="fas fa-euro-sign text-xl"></i>
</div>
<div class="ml-4">
<div class="text-2xl font-bold text-gray-800">€67,250</div>
<div class="text-gray-600">Total Value</div>
</div>
</div>
</div>
<div class="bg-white rounded-lg shadow-md p-6 card-hover">
<div class="flex items-center">
<div class="p-3 rounded-full bg-purple-100 text-purple-600">
<i class="fas fa-clock text-xl"></i>
</div>
<div class="ml-4">
<div class="text-2xl font-bold text-gray-800" id="avg-bids">24</div>
<div class="text-gray-600">Avg Bids</div>
</div>
</div>
</div>
</div>
<!-- Charts Section -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8 mb-8">
<!-- Categories Chart -->
<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-chart-pie mr-2 text-blue-600"></i>
Kavel Distribution by Category
</h3>
<div id="categories-chart" style="height: 300px;"></div>
</div>
<!-- Price Ranges Chart -->
<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-chart-bar mr-2 text-green-600"></i>
Price Distribution
</h3>
<div id="prices-chart" style="height: 300px;"></div>
</div>
</div>
<!-- Bid Activity Chart -->
<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-chart-line mr-2 text-purple-600"></i>
Bidding Activity Analysis
</h3>
<div id="bids-chart" style="height: 400px;"></div>
</div>
<!-- Kavel Details Table -->
<div class="bg-white rounded-lg shadow-md p-6">
<div class="flex justify-between items-center mb-6">
<h3 class="text-xl font-semibold text-gray-800">
<i class="fas fa-list mr-2 text-indigo-600"></i>
Kavel Details
</h3>
<div class="flex space-x-2">
<button id="export-json" class="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors">
<i class="fas fa-download mr-2"></i>Export JSON
</button>
<button id="export-csv" class="bg-green-600 text-white px-4 py-2 rounded-lg hover:bg-green-700 transition-colors">
<i class="fas fa-file-csv mr-2"></i>Export CSV
</button>
</div>
</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 tracking-wider">Kavel</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Category</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Current Bid</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Bids</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Location</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">End Date</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody id="kavels-table" class="bg-white divide-y divide-gray-200">
<!-- Table content will be populated by JavaScript -->
</tbody>
</table>
</div>
</div>
</main>
<!-- Footer -->
<footer class="bg-gray-800 text-white py-8 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>
Data extracted from Troostwijk Auctions - Industrial Equipment & Machinery Auctions
</p>
<p class="text-sm text-gray-400 mt-2">
Generated on: <span id="generation-date"></span>
</p>
</div>
</footer>
<script>
// Sample data (in real implementation, this would be loaded from the extracted files)
const kavelData = [
{
"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"
}
];
// Initialize dashboard
document.addEventListener('DOMContentLoaded', function() {
populateTable();
createCharts();
setupExportButtons();
document.getElementById('generation-date').textContent = new Date().toLocaleString();
});
function populateTable() {
const tableBody = document.getElementById('kavels-table');
kavelData.forEach(kavel => {
const row = document.createElement('tr');
row.className = 'hover:bg-gray-50';
row.innerHTML = `
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="ml-4">
<div class="text-sm font-medium text-gray-900">${kavel.title}</div>
<div class="text-sm text-gray-500">${kavel.id}</div>
</div>
</div>
</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 bg-blue-100 text-blue-800">
${kavel.category}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
${kavel.current_bid}
</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 bg-green-100 text-green-800">
${kavel.bid_count} bids
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
${kavel.location}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
${new Date(kavel.end_date).toLocaleDateString()} ${new Date(kavel.end_date).toLocaleTimeString()}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
<button onclick="showDetails('${kavel.id}')" class="text-indigo-600 hover:text-indigo-900 mr-3">
<i class="fas fa-eye"></i>
</button>
<a href="${kavel.url}" target="_blank" class="text-green-600 hover:text-green-900">
<i class="fas fa-external-link-alt"></i>
</a>
</td>
`;
tableBody.appendChild(row);
});
}
function createCharts() {
// Categories pie chart
const categoriesData = kavelData.reduce((acc, kavel) => {
acc[kavel.category] = (acc[kavel.category] || 0) + 1;
return acc;
}, {});
const categoriesTrace = {
values: Object.values(categoriesData),
labels: Object.keys(categoriesData),
type: 'pie',
marker: {
colors: ['#3b82f6', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6']
},
textinfo: 'label+percent',
textposition: 'auto'
};
Plotly.newPlot('categories-chart', [categoriesTrace], {
showlegend: false,
margin: { t: 0, b: 0, l: 0, r: 0 }
});
// Price ranges bar chart
const priceRanges = kavelData.reduce((acc, kavel) => {
const price = parseInt(kavel.current_bid.replace(/[€,]/g, ''));
let range;
if (price < 5000) range = 'Under €5,000';
else if (price < 15000) range = '€5,000 - €15,000';
else if (price < 25000) range = '€15,000 - €25,000';
else range = 'Over €25,000';
acc[range] = (acc[range] || 0) + 1;
return acc;
}, {});
const pricesTrace = {
x: Object.keys(priceRanges),
y: Object.values(priceRanges),
type: 'bar',
marker: {
color: '#10b981'
}
};
Plotly.newPlot('prices-chart', [pricesTrace], {
xaxis: { title: 'Price Range' },
yaxis: { title: 'Number of Kavels' },
margin: { t: 20, b: 80, l: 60, r: 20 }
});
// Bidding activity scatter plot
const bidsTrace = {
x: kavelData.map(k => k.title),
y: kavelData.map(k => parseInt(k.bid_count)),
mode: 'markers',
type: 'scatter',
marker: {
size: 12,
color: kavelData.map(k => parseInt(k.current_bid.replace(/[€,]/g, ''))),
colorscale: 'Viridis',
showscale: true,
colorbar: { title: 'Price (€)' }
},
text: kavelData.map(k => `${k.title}<br>Bids: ${k.bid_count}<br>Price: ${k.current_bid}`),
hovertemplate: '%{text}<extra></extra>'
};
Plotly.newPlot('bids-chart', [bidsTrace], {
xaxis: { title: 'Kavel', tickangle: -45 },
yaxis: { title: 'Number of Bids' },
margin: { t: 20, b: 150, l: 60, r: 80 }
});
}
function setupExportButtons() {
document.getElementById('export-json').addEventListener('click', function() {
downloadData(kavelData, 'troostwijk_kavels.json', 'application/json');
});
document.getElementById('export-csv').addEventListener('click', function() {
const csvData = convertToCSV(kavelData);
downloadData(csvData, 'troostwijk_kavels.csv', 'text/csv');
});
}
function downloadData(data, filename, mimeType) {
const blob = new Blob([typeof data === 'string' ? data : JSON.stringify(data, null, 2)],
{ type: mimeType });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
function convertToCSV(data) {
const headers = ['ID', 'Title', 'Category', 'Current Bid', 'Bid Count', 'Location', 'End Date'];
const csvContent = [
headers.join(','),
...data.map(row => [
row.id, row.title, row.category, row.current_bid,
row.bid_count, row.location, row.end_date
].join(','))
].join('\n');
return csvContent;
}
function showDetails(kavelId) {
const kavel = kavelData.find(k => k.id === kavelId);
if (kavel) {
alert(`Details for ${kavel.title}:\n\nDescription: ${kavel.description}\nCondition: ${kavel.condition}\nYear: ${kavel.year}\nSpecifications: ${JSON.stringify(kavel.specifications, null, 2)}`);
}
}
</script>
</body>
</html>

View File

@@ -0,0 +1,30 @@
{
"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": 1,
"Over \u20ac25,000": 1
},
"bid_activity": {
"Medium (10-24 bids)": 2,
"Low (1-9 bids)": 1,
"High (25-39 bids)": 1,
"Very High (40+ bids)": 1
},
"time_distribution": {}
}

View File

@@ -0,0 +1,6 @@
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 id title description current_bid bid_count end_date location auction_place category condition year images specifications url
2 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
3 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
4 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
5 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
6 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

View File

@@ -0,0 +1,120 @@
[
{
"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"
}
]

View File

@@ -0,0 +1,17 @@
package so;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;
import io.quarkus.runtime.StartupEvent;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@ApplicationScoped
public class AppLifecycle {
void onStart(@Observes StartupEvent ev) {
log.info("The application is starting...");
ImageService.main();
log.info("--- OpenCV loaded during startup.");
}
}

View File

@@ -0,0 +1,31 @@
package so;
import org.opencv.core.*;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
public class ImageGenerator {
public static void createTestImages() {
// Create a main image with shapes
Mat mainImage = new Mat(400, 600, CvType.CV_8UC3, new Scalar(240, 240, 240));
// Draw a red rectangle (our target object)
Imgproc.rectangle(mainImage,
new Point(150, 100),
new Point(250, 200),
new Scalar(0, 0, 255), -1);
// Draw some other shapes as distractors
Imgproc.circle(mainImage, new Point(450, 150), 50, new Scalar(255, 0, 0), -1);
Imgproc.rectangle(mainImage, new Point(350, 250), new Point(500, 300),
new Scalar(0, 255, 0), -1);
// Create the template (just the red rectangle)
Mat template = new Mat(100, 100, CvType.CV_8UC3, new Scalar(0, 0, 255));
// Save images
Imgcodecs.imwrite("test-images/main.jpg", mainImage);
Imgcodecs.imwrite("test-images/template.jpg", template);
}
}

View File

@@ -0,0 +1,178 @@
package so;
import lombok.extern.slf4j.Slf4j;
import nu.pattern.OpenCV;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Point;
import org.opencv.core.Rect;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import static so.ImageGenerator.createTestImages;
@Slf4j
public class ImageService {
static final String outputPath = "/tmp/detection_result.jpg";
String imagePath = System.getProperty("java.io.tmpdir") + "/detection_result.jpg";
public static void main() {
log.info("Starting Quarkus/OpenCV application");
log.info("Java version: {}", System.getProperty("java.version"));
log.info("OS: {} {}", System.getProperty("os.name"), System.getProperty("os.arch"));
try {
OpenCV.loadLocally();
log.info("OpenCV loaded successfully!");
log.info("OpenCV version: {}", Core.getVersionString());
// Test with generated images
testObjectDetection();
log.info("Application startup complete");
} catch (Exception e) {
log.error("Failed to start application", e);
System.exit(1);
}
}
private static void testObjectDetection() {
try {
// Create test images if they don't exist
if (!new File("test-images/main.jpg").exists()) {
createTestImages();
}
// Detect object
List<Rect> detections = detectObjectInImage(
"test-images/main.jpg",
"test-images/template.jpg"
);
log.info("Found {} object(s)", detections.size());
if (!detections.isEmpty()) {
log.info("First detection at: x={}, y={}, width={}, height={}",
detections.get(0).x, detections.get(0).y,
detections.get(0).width, detections.get(0).height);
}
} catch (Exception e) {
log.error("Object detection test failed", e);
}
}
/**
* Basic object detection using template matching
*
* @param imagePath Path to the main image
* @param templatePath Path to the template image (object to find)
* @return List of rectangles where the template was found
*/
public static List<Rect> detectObjectInImage(String imagePath, String templatePath) {
List<Rect> detections = new ArrayList<>();
try {
// Load the main image and template
Mat image = Imgcodecs.imread(imagePath);
Mat template = Imgcodecs.imread(templatePath);
// Log the paths being used for debugging
log.info("Looking for image at: {}", imagePath);
log.info("Looking for template at: {}", templatePath);
// Check if files exist before trying to load
File imageFile = new File(imagePath);
File templateFile = new File(templatePath);
if (!imageFile.exists() || !templateFile.exists()) {
log.error("Image files not found. Image exists: {}, Template exists: {}",
imageFile.exists(), templateFile.exists());
return detections;
}
log.info("Image size: {}x{}", image.width(), image.height());
log.info("Template size: {}x{}", template.width(), template.height());
// Convert to grayscale (better for template matching)
Mat grayImage = new Mat();
Mat grayTemplate = new Mat();
Imgproc.cvtColor(image, grayImage, Imgproc.COLOR_BGR2GRAY);
Imgproc.cvtColor(template, grayTemplate, Imgproc.COLOR_BGR2GRAY);
// Create result matrix
Mat result = new Mat();
int resultCols = image.cols() - template.cols() + 1;
int resultRows = image.rows() - template.rows() + 1;
result.create(resultRows, resultCols, CvType.CV_32FC1);
// Perform template matching
Imgproc.matchTemplate(grayImage, grayTemplate, result, Imgproc.TM_CCOEFF_NORMED);
// Define threshold for detection (0.8 = 80% similarity)
double threshold = 0.8;
// Find all matches above threshold
Core.MinMaxLocResult mmr = Core.minMaxLoc(result);
if (mmr.maxVal >= threshold) {
// For multiple detections, you would iterate through the result matrix
// Here we just take the best match
Point matchLoc = mmr.maxLoc;
Rect rect = new Rect(matchLoc, new Size(template.cols(), template.rows()));
detections.add(rect);
log.info("Object detected at position: ({}, {}) with confidence: {}",
matchLoc.x, matchLoc.y, mmr.maxVal);
// Draw rectangle around the detected object
Imgproc.rectangle(image, rect, new Scalar(0, 255, 0), 2);
// Save the result with detection
Imgcodecs.imwrite(outputPath, image);
log.info("Result saved to: {}", outputPath);
} else {
log.info("No object found above threshold");
}
// Clean up
image.release();
template.release();
grayImage.release();
grayTemplate.release();
result.release();
} catch (Exception e) {
log.error("Error in object detection: " + e.getMessage(), e);
}
return detections;
}
/**
* Simple edge detection example (alternative approach)
*/
public static void detectEdges(String imagePath) {
try {
Mat image = Imgcodecs.imread(imagePath);
Mat gray = new Mat();
Mat edges = new Mat();
Imgproc.cvtColor(image, gray, Imgproc.COLOR_BGR2GRAY);
Imgproc.Canny(gray, edges, 100, 200);
Imgcodecs.imwrite("edges_detected.jpg", edges);
log.info("Edge detection completed");
image.release();
gray.release();
edges.release();
} catch (Exception e) {
log.error("Error in edge detection: " + e.getMessage());
}
}
}

View File

@@ -0,0 +1,85 @@
package so;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Map;
@Slf4j
@Path("/api")
public class StatusResource {
private static final DateTimeFormatter FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z")
.withZone(ZoneId.systemDefault());
@ConfigProperty(name = "application.version", defaultValue = "1.0-SNAPSHOT")
String appVersion;
@ConfigProperty(name = "application.groupId")
String groupId;
@ConfigProperty(name = "application.artifactId")
String artifactId;
@ConfigProperty(name = "application.version")
String version;
// Java 16+ Record for structured response
public record StatusResponse(
String groupId,
String artifactId,
String version,
String status,
String timestamp,
String mvnVersion,
String javaVersion,
String os,
String openCvVersion
) { }
@GET
@Path("/status")
@Produces(MediaType.APPLICATION_JSON)
public StatusResponse getStatus() {
log.info("Status endpoint called");
return new StatusResponse(groupId, artifactId, version,
"running",
FORMATTER.format(Instant.now()),
appVersion,
System.getProperty("java.version"),
System.getProperty("os.name") + " " + System.getProperty("os.arch"),
getOpenCvVersion()
);
}
@GET
@Path("/hello")
@Produces(MediaType.APPLICATION_JSON)
public Map<String, String> sayHello() {
log.info("hello endpoint called");
return Map.of(
"message", "Hello from Scrape-UI!",
"timestamp", FORMATTER.format(Instant.now()),
"openCvVersion", getOpenCvVersion()
);
}
private String getOpenCvVersion() {
try {
// Load OpenCV if not already loaded (safe to call multiple times)
nu.pattern.OpenCV.loadLocally();
return org.opencv.core.Core.VERSION;
} catch (Exception e) {
return "4.9.0 (default)";
}
}
}

View File

@@ -0,0 +1,7 @@
<?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>

View File

@@ -0,0 +1,224 @@
<!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>

View File

@@ -0,0 +1,39 @@
# Application Configuration
# Values will be injected from pom.xml during build
quarkus.application.name=${project.artifactId}
quarkus.application.version=${project.version}
# Custom properties for groupId if needed
application.groupId=${project.groupId}
application.artifactId=${project.artifactId}
application.version=${project.version}
# HTTP Configuration
quarkus.http.port=8081
# ========== DEVELOPMENT (quarkus:dev) ==========
%dev.quarkus.http.host=127.0.0.1
# ========== PRODUCTION (Docker/JAR) ==========
%prod.quarkus.http.host=0.0.0.0
# ========== TEST PROFILE ==========
%test.quarkus.http.host=localhost
# Enable CORS for frontend development
quarkus.http.cors=true
quarkus.http.cors.origins=*
quarkus.http.cors.methods=GET,POST,PUT,DELETE,OPTIONS
quarkus.http.cors.headers=accept,authorization,content-type,x-requested-with
# Logging Configuration
quarkus.log.console.format=%d{HH:mm:ss} %-5p [%c{2.}] (%t) %s%e%n
quarkus.log.console.level=INFO
# Development mode settings
%dev.quarkus.log.console.level=DEBUG
%dev.quarkus.live-reload.instrumentation=true
# Production optimizations
%prod.quarkus.http.enable-compression=true
# Static resources
quarkus.http.enable-compression=true
quarkus.rest.path=/
quarkus.http.root-path=/

BIN
test-images/lena.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

BIN
test-images/main.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
test-images/template.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 827 B

20
workflows/maven.yml Normal file
View File

@@ -0,0 +1,20 @@
name: Publish to Gitea Package Registry
on:
push:
tags:
- 'v*'
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up JDK 25
uses: actions/setup-java@v4
with:
java-version: '25'
distribution: 'temurin'
- name: Publish with Maven
run: mvn --batch-mode clean deploy
env:
GITEA_TOKEN: ${{ secrets.EA_PUBLISH_TOKEN }}