Init
This commit is contained in:
45
.gitignore
vendored
Normal file
45
.gitignore
vendored
Normal 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
27
Dockerfile
Normal 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
166
README.md
Normal 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
34
_wiki/Dockerfile.bak
Normal 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
38
_wiki/check-jar.ps1
Normal 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
130
_wiki/domain-information.md
Normal 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
11
docker-compose.yml
Normal 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
215
pom.xml
Normal 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
473
public/index.html
Normal 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>
|
||||
30
public/troostwijk_analysis_20251126_152413.json
Normal file
30
public/troostwijk_analysis_20251126_152413.json
Normal 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": {}
|
||||
}
|
||||
6
public/troostwijk_kavels_20251126_152413.csv
Normal file
6
public/troostwijk_kavels_20251126_152413.csv
Normal 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
|
||||
|
120
public/troostwijk_kavels_20251126_152413.json
Normal file
120
public/troostwijk_kavels_20251126_152413.json
Normal 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"
|
||||
}
|
||||
]
|
||||
17
src/main/java/so/AppLifecycle.java
Normal file
17
src/main/java/so/AppLifecycle.java
Normal 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.");
|
||||
}
|
||||
}
|
||||
31
src/main/java/so/ImageGenerator.java
Normal file
31
src/main/java/so/ImageGenerator.java
Normal 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);
|
||||
}
|
||||
}
|
||||
178
src/main/java/so/ImageService.java
Normal file
178
src/main/java/so/ImageService.java
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
85
src/main/java/so/StatusResource.java
Normal file
85
src/main/java/so/StatusResource.java
Normal 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)";
|
||||
}
|
||||
}
|
||||
}
|
||||
7
src/main/resources/META-INF/resources/beans.xml
Normal file
7
src/main/resources/META-INF/resources/beans.xml
Normal 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>
|
||||
224
src/main/resources/META-INF/resources/index.html
Normal file
224
src/main/resources/META-INF/resources/index.html
Normal 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>
|
||||
39
src/main/resources/application.properties
Normal file
39
src/main/resources/application.properties
Normal 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
BIN
test-images/lena.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 90 KiB |
BIN
test-images/main.jpg
Normal file
BIN
test-images/main.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
BIN
test-images/template.jpg
Normal file
BIN
test-images/template.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 827 B |
20
workflows/maven.yml
Normal file
20
workflows/maven.yml
Normal 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 }}
|
||||
Reference in New Issue
Block a user