diff --git a/.env.example b/.env.example
new file mode 100644
index 00000000..200ce09c
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,43 @@
+# DAVE Security Configuration Template
+# Copy this file to .env and configure for your environment
+
+# Flask Configuration
+FLASK_ENV=production
+FLASK_DEBUG=False
+FLASK_SECRET_KEY=your-secret-key-here-min-32-chars
+
+# Session Security
+SESSION_COOKIE_SECURE=True
+SESSION_LIFETIME_HOURS=24
+
+# CORS Configuration
+CORS_ENABLED=True
+CORS_ORIGINS=http://localhost:3000,https://your-domain.com
+
+# Upload Limits
+MAX_UPLOAD_SIZE_MB=100
+
+# Rate Limiting
+RATELIMIT_ENABLED=True
+RATELIMIT_DEFAULT=100 per hour
+REDIS_URL=redis://localhost:6379
+
+# Authentication (future)
+AUTH_ENABLED=False
+AUTH_METHOD=jwt
+JWT_SECRET_KEY=your-jwt-secret-key
+JWT_TOKEN_LIFETIME_HOURS=1
+
+# API Keys (comma separated)
+API_KEY_ENABLED=False
+API_KEYS=key1,key2,key3
+
+# Shutdown Protection
+PROTECT_SHUTDOWN=True
+SHUTDOWN_PASSWORD=secure-shutdown-password
+
+# Logging
+SANITIZE_LOGS=True
+
+# Database (future)
+DATABASE_URL=sqlite:///dave.db
\ No newline at end of file
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 00000000..887a2c18
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,2 @@
+# SCM syntax highlighting & preventing 3-way merges
+pixi.lock merge=binary linguist-language=YAML linguist-generated=true
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 00000000..12dd209b
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,42 @@
+version: 2
+updates:
+ # Python dependencies via pixi
+ - package-ecosystem: "pip"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ day: "monday"
+ open-pull-requests-limit: 5
+ labels:
+ - "dependencies"
+ - "python"
+ commit-message:
+ prefix: "chore"
+ include: "scope"
+
+ # JavaScript dependencies
+ - package-ecosystem: "npm"
+ directory: "/src/main/js/electron"
+ schedule:
+ interval: "weekly"
+ day: "monday"
+ open-pull-requests-limit: 5
+ labels:
+ - "dependencies"
+ - "javascript"
+ commit-message:
+ prefix: "chore"
+ include: "scope"
+
+ # GitHub Actions
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ day: "monday"
+ labels:
+ - "dependencies"
+ - "ci"
+ commit-message:
+ prefix: "ci"
+ include: "scope"
\ No newline at end of file
diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml
new file mode 100644
index 00000000..e558d234
--- /dev/null
+++ b/.github/workflows/cd.yml
@@ -0,0 +1,163 @@
+name: CD
+
+on:
+ push:
+ tags:
+ - 'v*'
+ release:
+ types: [published]
+ workflow_dispatch:
+ inputs:
+ create_release:
+ description: 'Create release'
+ required: true
+ default: false
+ type: boolean
+
+jobs:
+ build:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ include:
+ - os: ubuntu-latest
+ platform: linux
+ artifact_name: dave-linux
+ - os: macos-latest
+ platform: macos
+ artifact_name: dave-macos
+ - os: windows-latest
+ platform: windows
+ artifact_name: dave-windows
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Setup Pixi
+ uses: prefix-dev/setup-pixi@v0.6.0
+ with:
+ pixi-version: v0.40.0
+ cache: true
+
+ - name: Install dependencies
+ run: |
+ pixi install
+ pixi run install-node-deps
+
+ - name: Run tests (quick)
+ run: pixi run test
+
+ - name: Build application
+ run: python build.py --platform ${{ matrix.platform }} --legacy --no-tests
+
+ - name: Upload build artifacts
+ uses: actions/upload-artifact@v4
+ with:
+ name: ${{ matrix.artifact_name }}
+ path: |
+ dist/
+ build/
+ retention-days: 30
+
+ - name: Package artifacts for release
+ if: startsWith(github.ref, 'refs/tags/')
+ run: |
+ # Legacy builder creates zip files in dist/
+ if [ "${{ matrix.platform }}" = "linux" ]; then
+ cp dist/*.zip dave-${{ matrix.platform }}.zip 2>/dev/null || echo "No zip files found for Linux"
+ find build/ -name "*.deb" -exec cp {} dave-${{ matrix.platform }}.deb \; 2>/dev/null || echo "No deb files found"
+ find build/ -name "*.rpm" -exec cp {} dave-${{ matrix.platform }}.rpm \; 2>/dev/null || echo "No rpm files found"
+ elif [ "${{ matrix.platform }}" = "macos" ]; then
+ cp dist/*.zip dave-${{ matrix.platform }}.zip 2>/dev/null || echo "No zip files found for macOS"
+ find build/ -name "*.dmg" -exec cp {} dave-${{ matrix.platform }}.dmg \; 2>/dev/null || echo "No dmg files found"
+ elif [ "${{ matrix.platform }}" = "windows" ]; then
+ cp dist/*.zip dave-${{ matrix.platform }}.zip 2>/dev/null || echo "No zip files found for Windows"
+ find build/ -name "*.exe" -exec cp {} dave-${{ matrix.platform }}.exe \; 2>/dev/null || echo "No exe files found"
+ fi
+ shell: bash
+
+ - name: Upload release artifacts
+ uses: actions/upload-artifact@v4
+ if: startsWith(github.ref, 'refs/tags/')
+ with:
+ name: release-${{ matrix.artifact_name }}
+ path: |
+ dave-${{ matrix.platform }}.*
+ retention-days: 90
+
+ release:
+ needs: build
+ runs-on: ubuntu-latest
+ if: startsWith(github.ref, 'refs/tags/') || github.event.inputs.create_release == 'true'
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Download all artifacts
+ uses: actions/download-artifact@v4
+ with:
+ path: ./artifacts
+
+ - name: Display structure of downloaded files
+ run: ls -la ./artifacts/
+
+ - name: Create Release
+ id: create_release
+ uses: actions/create-release@v1
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ tag_name: ${{ github.ref_name }}
+ release_name: DAVE ${{ github.ref_name }}
+ body: |
+ ## DAVE ${{ github.ref_name }}
+
+ ### Changes
+ - See commit history for detailed changes
+
+ ### Downloads
+ - **Linux**: dave-linux.zip
+ - **macOS**: dave-macos.zip
+ - **Windows**: dave-windows.zip
+
+ ### Installation
+ 1. Download the appropriate package for your platform
+ 2. Extract the archive
+ 3. Follow the installation instructions in the README
+
+ ### Requirements
+ - Python 3.13+
+ - 8GB RAM minimum
+ - 10GB disk space
+ draft: false
+ prerelease: false
+
+ - name: Upload Linux Release Asset
+ uses: actions/upload-release-asset@v1
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ upload_url: ${{ steps.create_release.outputs.upload_url }}
+ asset_path: ./artifacts/release-dave-linux/dave-linux.zip
+ asset_name: dave-linux.zip
+ asset_content_type: application/zip
+
+ - name: Upload macOS Release Asset
+ uses: actions/upload-release-asset@v1
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ upload_url: ${{ steps.create_release.outputs.upload_url }}
+ asset_path: ./artifacts/release-dave-macos/dave-macos.zip
+ asset_name: dave-macos.zip
+ asset_content_type: application/zip
+
+ - name: Upload Windows Release Asset
+ uses: actions/upload-release-asset@v1
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ upload_url: ${{ steps.create_release.outputs.upload_url }}
+ asset_path: ./artifacts/release-dave-windows/dave-windows.zip
+ asset_name: dave-windows.zip
+ asset_content_type: application/zip
\ No newline at end of file
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 00000000..0e5c992e
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,80 @@
+name: CI
+
+on:
+ push:
+ branches: [ modernization-2025, master ]
+ pull_request:
+ branches: [ modernization-2025, master ]
+
+jobs:
+ test:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ os: [ubuntu-latest, macos-latest, windows-latest]
+ python-version: ['3.13']
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Setup Pixi
+ uses: prefix-dev/setup-pixi@v0.6.0
+ with:
+ pixi-version: v0.40.0
+ cache: true
+
+ - name: Install dependencies
+ run: |
+ pixi install
+ pixi run install-node-deps
+
+ - name: Run Python linting
+ run: pixi run lint
+
+ - name: Run Python tests
+ run: pixi run test
+
+ - name: Run Python tests with coverage
+ run: pixi run test-coverage
+
+ - name: Install Playwright browsers
+ run: pixi run test-e2e-install
+
+ - name: Run E2E tests
+ run: pixi run test-e2e
+
+ - name: Upload coverage reports
+ uses: codecov/codecov-action@v4
+ if: matrix.os == 'ubuntu-latest'
+ with:
+ file: ./coverage.xml
+ flags: unittests
+ name: codecov-umbrella
+ fail_ci_if_error: false
+
+ security:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Setup Pixi
+ uses: prefix-dev/setup-pixi@v0.6.0
+ with:
+ pixi-version: v0.40.0
+ cache: true
+
+ - name: Install dependencies
+ run: pixi install
+
+ - name: Run security checks
+ run: |
+ pixi run lint
+ # Additional security checks can be added here
+
+ - name: Check for secrets
+ uses: trufflesecurity/trufflehog@main
+ with:
+ path: ./
+ base: main
+ head: HEAD
+ extra_args: --debug --only-verified
\ No newline at end of file
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 00000000..20a30940
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,145 @@
+name: Release
+
+on:
+ push:
+ tags:
+ - 'v*'
+
+env:
+ PIXI_VERSION: "0.39.0"
+ NODE_VERSION: "22.16.0"
+
+jobs:
+ build:
+ name: Build ${{ matrix.os }}
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - os: ubuntu-latest
+ platform: linux
+ artifact_pattern: "*.AppImage"
+ - os: macos-latest
+ platform: macos
+ artifact_pattern: "*.dmg"
+ - os: windows-latest
+ platform: windows
+ artifact_pattern: "*.exe"
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: ${{ env.NODE_VERSION }}
+
+ - name: Install Pixi
+ uses: prefix-dev/setup-pixi@v0.8.1
+ with:
+ pixi-version: v${{ env.PIXI_VERSION }}
+
+ - name: Install dependencies
+ run: |
+ pixi install
+ pixi run install-node-deps
+
+ - name: Build application
+ run: pixi run build-dev
+ env:
+ BUILD_NUMBER: ${{ github.run_number }}
+ JOB_NAME: "dave-release"
+
+ - name: Package application
+ run: pixi run dist
+
+ - name: Find built artifact
+ id: find_artifact
+ shell: bash
+ run: |
+ if [ "${{ matrix.platform }}" == "linux" ]; then
+ ARTIFACT=$(find dist -name "*.AppImage" -type f | head -1)
+ elif [ "${{ matrix.platform }}" == "macos" ]; then
+ ARTIFACT=$(find dist -name "*.dmg" -type f | head -1)
+ elif [ "${{ matrix.platform }}" == "windows" ]; then
+ ARTIFACT=$(find dist -name "*.exe" -type f | head -1)
+ fi
+
+ if [ -z "$ARTIFACT" ]; then
+ echo "No artifact found!"
+ ls -la dist/ || echo "dist directory not found"
+ exit 1
+ fi
+
+ echo "artifact_path=$ARTIFACT" >> $GITHUB_OUTPUT
+ echo "artifact_name=$(basename $ARTIFACT)" >> $GITHUB_OUTPUT
+ echo "Found artifact: $ARTIFACT"
+
+ - name: Upload build artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: dave-${{ matrix.platform }}
+ path: ${{ steps.find_artifact.outputs.artifact_path }}
+ retention-days: 5
+
+ release:
+ name: Create Release
+ needs: build
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Get version from tag
+ id: get_version
+ run: echo "version=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
+
+ - name: Download all artifacts
+ uses: actions/download-artifact@v4
+ with:
+ path: release-artifacts
+
+ - name: Prepare release assets
+ run: |
+ mkdir -p release-files
+ find release-artifacts -type f \( -name "*.AppImage" -o -name "*.dmg" -o -name "*.exe" \) -exec cp {} release-files/ \;
+ ls -la release-files/
+
+ - name: Generate changelog
+ id: changelog
+ run: |
+ echo "## What's Changed in v${{ steps.get_version.outputs.version }}" > changelog.md
+ echo "" >> changelog.md
+
+ # Get previous tag
+ PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
+
+ if [ -n "$PREV_TAG" ]; then
+ echo "### Commits" >> changelog.md
+ git log --pretty=format:"- %s (%h)" ${PREV_TAG}..HEAD >> changelog.md
+ else
+ echo "### Commits" >> changelog.md
+ git log --pretty=format:"- %s (%h)" -20 >> changelog.md
+ fi
+
+ echo "" >> changelog.md
+ echo "" >> changelog.md
+ echo "---" >> changelog.md
+ echo "*Full changelog: https://github.com/${{ github.repository }}/compare/${PREV_TAG}...v${{ steps.get_version.outputs.version }}*" >> changelog.md
+
+ - name: Create GitHub Release
+ uses: softprops/action-gh-release@v2
+ with:
+ name: DAVE v${{ steps.get_version.outputs.version }}
+ body_path: changelog.md
+ draft: true
+ prerelease: ${{ contains(github.ref, '-alpha') || contains(github.ref, '-beta') || contains(github.ref, '-rc') }}
+ files: release-files/*
+ fail_on_unmatched_files: false
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.gitignore b/.gitignore
index 9d81ced4..7f78f981 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,4 @@
-*~
-work*
+# .gitignore for Python and related environments
build
__pycache__
flaskserver.log
@@ -9,3 +8,32 @@ flaskserver.log
*.nc
.cache
.hypothesis
+
+# pixi environments
+.pixi
+*.egg-info
+
+# Test files
+.coverage
+temp_docs/
+test-results/
+playwright-report/
+screenshots/
+tests/playwright-report/
+tests/screenshots/
+.DS_Store
+CLAUDE.md
+
+# Build outputs
+src/main/js/electron/out/
+dist/
+
+# Node.js dependencies
+node_modules/
+src/main/js/electron/node_modules/
+*.dmg
+*.zip
+*.deb
+*.rpm
+*.exe
+*.AppImage
\ No newline at end of file
diff --git a/.nvmrc b/.nvmrc
new file mode 100644
index 00000000..156ca6d3
--- /dev/null
+++ b/.nvmrc
@@ -0,0 +1 @@
+22.16.0
\ No newline at end of file
diff --git a/.releaserc.json b/.releaserc.json
new file mode 100644
index 00000000..95171e19
--- /dev/null
+++ b/.releaserc.json
@@ -0,0 +1,33 @@
+{
+ "branches": ["master", "main"],
+ "plugins": [
+ "@semantic-release/commit-analyzer",
+ "@semantic-release/release-notes-generator",
+ [
+ "@semantic-release/changelog",
+ {
+ "changelogFile": "CHANGELOG.md"
+ }
+ ],
+ [
+ "@semantic-release/exec",
+ {
+ "prepareCmd": "python scripts/update-version.py ${nextRelease.version}"
+ }
+ ],
+ [
+ "@semantic-release/git",
+ {
+ "assets": [
+ "CHANGELOG.md",
+ "pyproject.toml",
+ "src/main/python/__version__.py",
+ "src/main/js/electron/package.json",
+ "src/main/js/electron/package-lock.json"
+ ],
+ "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
+ }
+ ],
+ "@semantic-release/github"
+ ]
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index 5787c456..678eb655 100644
--- a/README.md
+++ b/README.md
@@ -1,34 +1,117 @@
-# DAVE
+# DAVE - Data Analysis of Variable Events
-DAVE stands for Data Analysis of Variable Events, which is a GUI built on top of
-the [Stingray library](https://github.com/StingraySoftware/stingray). It is
-intended to be used by astronomers for time-series analysis in general, and
-analysis of variable sources in particular.
+
+

+
-The goal is to enable scientific exploration of astronomical X-Ray
-observations and to analyse this data in a graphical environment.
+A modern desktop GUI application for astronomical X-ray timing analysis built on the Stingray library. DAVE provides an intuitive interface for analyzing variable X-ray sources with enterprise-grade performance and security.
+
+
+
-## Get Started
+## Quick Start
-* Clone the project `$ git clone https://github.com/StingraySoftware/dave`
-* Install a Python virtual env and a compatible version of node: `$ source setup/setup.bash`
-* Run the application for development: `$ setup/run_gui.bash`
-* Or run the build script for Linux_X64 `$ setup/build_linux-x64.bash` for getting the distributable at DAVE build folder.
+### Prerequisites
+- **Pixi Package Manager** ([install here](https://pixi.sh))
+### Installation
-You will see that there's plenty left to do!
+```bash
+# Clone the repository
+git clone https://github.com/StingraySoftware/dave.git
+cd dave
-NOTE: Mac OSX dependencies
-* At least Homebrew installed (http://brew.sh/) or MacPorts installed (https://www.macports.org)
-* If MacPorts will be used, you have two available options:
-* 1 - Install LibMagic by yourself running this MacPorts command `sudo /opt/local/bin/port install file` on the terminal and then launch DAVE running `DAVEApp.app/Contents/MacOS/DAVEApp`.
-* 2 - Launch DAVE as root running this command on the terminal: `sudo DAVEApp.app/Contents/MacOS/DAVEApp`
+# Switch to modernization branch (required)
+git checkout modernization-2025
+# Install Python environment and dependencies
+pixi install
-## Contribute
+# Install Node.js dependencies for Electron frontend
+pixi run install-node-deps
-Please talk to us! We use Slack to discuss the work. Use http://slack-invite.timelabtechnologies.com to self-invite yourself on the slack. Also, feel free to contact us at info@timelabtechnologies.com .
+# Verify installation
+pixi run test # Python tests
+pixi run test-e2e # End-to-end tests (requires pixi run test-e2e-install first)
+```
-The recorded open issues for DAVE are in [JIRA](https://timelabdev.com/jira/projects/DAVE). More information about communication in the project can be found in [Confluence](https://timelabdev.com/wiki/display/DAVE/Source+code+and+communication).
+### Running the Application
+
+```bash
+# Start the full application (recommended)
+pixi run electron
+
+# Alternative: Start components separately
+pixi run server # Flask backend only
+# Then in another terminal:
+cd src/main/js/electron && npm start # Electron frontend
+```
+
+## Architecture
+
+DAVE uses a hybrid client-server architecture:
+
+```
+┌─────────────────────────────────────────┐
+│ ELECTRON FRONTEND │
+│ (Desktop Wrapper + Web UI) │
+│ │
+│ ┌─────────────┐ ┌─────────────┐ │
+│ │ Main │ │ Renderer │ │
+│ │ Process │◄──►│ Process │ │
+│ │ (Node.js) │ │ (Chromium) │ │
+│ └─────────────┘ └─────────────┘ │
+└─────────────┬───────────────────────────┘
+ │ HTTP Requests (localhost:5001)
+ │
+┌─────────────▼───────────────────────────┐
+│ PYTHON BACKEND │
+│ (Flask REST API) │
+│ │
+│ ┌─────────────┐ ┌─────────────┐ │
+│ │ Flask │ │ Scientific │ │
+│ │ Server │◄──►│ Engine │ │
+│ │ (Routes) │ │ (Stingray) │ │
+│ └─────────────┘ └─────────────┘ │
+└─────────────────────────────────────────┘
+```
+
+## Development Commands
+
+### Code Quality & Linting
+
+```bash
+# Python linting (uses ruff)
+pixi run lint # Check Python code for issues
+pixi run format # Auto-format Python code
+
+# Fix all linting issues before committing
+pixi run lint # Must show 0 errors before commit
+```
+
+### Building the Application
+
+```bash
+# Build for current platform (macOS/Linux/Windows)
+pixi run build # Full build with tests
+pixi run build --no-tests # Skip tests during build
+
+# Build for specific platforms
+python build.py --platform linux --no-tests # Linux build
+python build.py --platform windows --no-tests # Windows build
+python build.py --platform macos --no-tests # macOS build
+
+# Use legacy Electron Builder instead of Forge
+python build.py --legacy --no-tests
+
+# Clean build artifacts
+python build.py --clean
+```
+
+### Build Output Locations
+
+After building, find your packages at:
+- **macOS**: `src/main/js/electron/out/make/DAVE.dmg`
+- **Linux**: `src/main/js/electron/out/make/deb/` and `src/main/js/electron/out/make/rpm/`
+- **Windows**: `src/main/js/electron/out/make/squirrel.windows/`
-Fork and pull request away!
diff --git a/build.config.json b/build.config.json
new file mode 100644
index 00000000..cd65f4b8
--- /dev/null
+++ b/build.config.json
@@ -0,0 +1,120 @@
+{
+ "appName": "DAVEApp",
+ "productName": "DAVE - Data Analysis of Variable Events",
+ "description": "Desktop GUI application for astronomical X-ray data analysis",
+ "copyright": "Copyright © 2025 StingRay Software",
+ "homepage": "https://github.com/StingraySoftware/dave",
+
+ "platforms": {
+ "linux": {
+ "target": "AppImage",
+ "category": "Science",
+ "icon": "src/main/resources/static/img/icon.png",
+ "desktop": {
+ "Name": "DAVE",
+ "Comment": "Astronomical X-ray data analysis",
+ "Categories": "Science;Astronomy;DataVisualization;",
+ "Keywords": "astronomy;x-ray;data;analysis;stingray;"
+ }
+ },
+ "macos": {
+ "target": "dmg",
+ "category": "public.app-category.education",
+ "icon": "src/main/resources/static/img/icon.icns",
+ "bundleId": "science.stingray.dave",
+ "hardenedRuntime": true,
+ "gatekeeperAssess": false,
+ "entitlements": "build/entitlements.mac.plist",
+ "entitlementsInherit": "build/entitlements.mac.plist"
+ },
+ "windows": {
+ "target": ["nsis", "portable"],
+ "icon": "src/main/resources/static/img/icon.ico",
+ "publisherName": "StingRay Software",
+ "verifyUpdateCodeSignature": false,
+ "requestedExecutionLevel": "asInvoker"
+ }
+ },
+
+ "directories": {
+ "output": "build",
+ "buildResources": "build-resources"
+ },
+
+ "files": [
+ "**/*",
+ "!**/*.ts",
+ "!*.code-workspace",
+ "!LICENSE",
+ "!README.md",
+ "!**/__pycache__",
+ "!**/node_modules/*",
+ "!src/test",
+ "!setup",
+ "!kubernetes",
+ "!.github",
+ "!scripts",
+ "!docs"
+ ],
+
+ "python": {
+ "version": "3.13",
+ "packages": [
+ "flask>=3.1",
+ "numpy>=2.2",
+ "astropy>=7.0",
+ "numba>=0.61",
+ "stingray>=2.2.7",
+ "hendrics>=8.1",
+ "scipy>=1.15",
+ "matplotlib>=3.10",
+ "gevent>=25.5",
+ "pillow",
+ "python-magic>=0.4.27",
+ "pylru>=1.2.1",
+ "flask-cors>=6.0.0",
+ "flask-session>=0.8.0"
+ ],
+ "bundleMethod": "pixi"
+ },
+
+ "publish": {
+ "provider": "github",
+ "owner": "StingraySoftware",
+ "repo": "dave",
+ "releaseType": "release"
+ },
+
+ "nsis": {
+ "oneClick": false,
+ "perMachine": false,
+ "allowToChangeInstallationDirectory": true,
+ "license": "LICENSE",
+ "createDesktopShortcut": true,
+ "createStartMenuShortcut": true
+ },
+
+ "dmg": {
+ "contents": [
+ {
+ "x": 110,
+ "y": 150
+ },
+ {
+ "x": 480,
+ "y": 150,
+ "type": "link",
+ "path": "/Applications"
+ }
+ ]
+ },
+
+ "appImage": {
+ "license": "LICENSE",
+ "category": "Science",
+ "desktop": {
+ "Name": "DAVE",
+ "Comment": "Astronomical X-ray data analysis"
+ }
+ }
+}
\ No newline at end of file
diff --git a/build.py b/build.py
new file mode 100755
index 00000000..91e4599a
--- /dev/null
+++ b/build.py
@@ -0,0 +1,408 @@
+#!/usr/bin/env python3
+"""
+Modern build system for DAVE - Data Analysis of Variable Events
+Replaces legacy bash scripts with cross-platform Python solution
+"""
+
+import argparse
+import json
+import os
+import platform
+import shutil
+import subprocess
+import sys
+import zipfile
+from datetime import datetime
+from pathlib import Path
+from typing import Dict, List, Optional
+
+
+class DaveBuilder:
+ """Main builder class for DAVE application"""
+
+ def __init__(self, platform_name: Optional[str] = None):
+ self.platform_name = platform_name or self._detect_platform()
+ self.project_root = Path(__file__).parent
+ self.build_dir = self.project_root / "build"
+ self.dist_dir = self.project_root / "dist"
+ self.version = self._generate_version()
+ self.electron_dir = self.project_root / "src" / "main" / "js" / "electron"
+
+ def _detect_platform(self) -> str:
+ """Detect the current platform"""
+ system = platform.system().lower()
+ if system == "darwin":
+ return "macos"
+ elif system == "linux":
+ return "linux"
+ elif system == "windows":
+ return "windows"
+ else:
+ raise ValueError(f"Unsupported platform: {system}")
+
+ def _generate_version(self) -> str:
+ """Generate semantic version string from git and timestamp"""
+ try:
+ # Get git commit hash
+ commit = subprocess.check_output(
+ ["git", "rev-parse", "--short", "HEAD"],
+ cwd=self.project_root,
+ text=True
+ ).strip()
+ except (subprocess.CalledProcessError, FileNotFoundError):
+ commit = "unknown"
+
+ # Generate timestamp components for semantic versioning
+ now = datetime.now()
+ major = 2 # Project version
+ minor = now.year - 2020 # Years since 2020
+ patch = now.month * 100 + now.day # MMDD format
+
+ # Check for CI build number
+ build_number = os.environ.get("BUILD_NUMBER")
+ job_name = os.environ.get("JOB_NAME", "dave")
+
+ if build_number:
+ return f"{major}.{minor}.{build_number}"
+ else:
+ # Use semantic versioning: MAJOR.MINOR.PATCH-PRERELEASE
+ return f"{major}.{minor}.{patch}-dev.{commit}"
+
+ def _update_package_json(self) -> None:
+ """Update version in package.json"""
+ package_path = self.electron_dir / "package.json"
+ with open(package_path, "r") as f:
+ data = json.load(f)
+
+ data["version"] = self.version
+
+ with open(package_path, "w") as f:
+ json.dump(data, f, indent=2)
+ f.write("\n") # Add trailing newline
+
+ print(f"Updated package.json version to: {self.version}")
+
+ def _run_command(self, cmd: List[str], cwd: Optional[Path] = None) -> None:
+ """Run a command with error handling"""
+ print(f"Running: {' '.join(cmd)}")
+ result = subprocess.run(
+ cmd,
+ cwd=cwd or self.project_root,
+ check=True,
+ capture_output=True,
+ text=True
+ )
+ if result.stdout:
+ print(result.stdout)
+ if result.stderr:
+ print(result.stderr, file=sys.stderr)
+
+ def clean(self) -> None:
+ """Clean build artifacts"""
+ print("Cleaning build artifacts...")
+
+ # Remove build directories
+ for dir_path in [self.build_dir, self.dist_dir]:
+ if dir_path.exists():
+ shutil.rmtree(dir_path)
+ print(f"Removed: {dir_path}")
+
+ # Clean node_modules if requested
+ node_modules = self.electron_dir / "node_modules"
+ if node_modules.exists() and input("Remove node_modules? (y/N): ").lower() == "y":
+ shutil.rmtree(node_modules)
+ print(f"Removed: {node_modules}")
+
+ def install_dependencies(self) -> None:
+ """Install Node.js dependencies"""
+ print("Installing Node.js dependencies...")
+ self._run_command(["npm", "install"], cwd=self.electron_dir)
+
+ def build_electron(self, use_forge: bool = False) -> None:
+ """Build Electron application"""
+ print(f"Building Electron app for {self.platform_name}...")
+
+ # Update package.json version
+ self._update_package_json()
+
+ if use_forge:
+ # Use Electron Forge
+ print("Using Electron Forge for building...")
+
+ # Package the app first
+ self._run_command(["npm", "run", "forge:package"], cwd=self.electron_dir)
+
+ # Then create installers
+ forge_targets = {
+ "linux": "forge:make-linux",
+ "macos": "forge:make-mac",
+ "windows": "forge:make-win"
+ }
+
+ forge_target = forge_targets.get(self.platform_name)
+ if forge_target:
+ self._run_command(["npm", "run", forge_target], cwd=self.electron_dir)
+ else:
+ # Use Electron Builder (legacy)
+ print("Using Electron Builder (legacy)...")
+
+ targets = {
+ "linux": "build-linux",
+ "macos": "build-mac",
+ "windows": "build-win"
+ }
+
+ build_target = targets.get(self.platform_name)
+ if not build_target:
+ raise ValueError(f"No build target for platform: {self.platform_name}")
+
+ # Run Electron build
+ self._run_command(["npm", "run", build_target], cwd=self.electron_dir)
+
+ def copy_resources(self) -> None:
+ """Copy additional resources to build output"""
+ print("Copying resources...")
+
+ # Detect current architecture
+ import platform
+ arch = platform.machine().lower()
+ if arch == 'aarch64':
+ arch = 'arm64'
+ elif arch in ['x86_64', 'amd64']:
+ arch = 'x64'
+
+ # Platform-specific resource paths (using actual electron-builder output structure)
+ if self.platform_name == "linux":
+ resources_dir = self.project_root / "build" / f"linux-{arch}" / "resources"
+ elif self.platform_name == "macos":
+ resources_dir = (
+ self.project_root / "build" / f"mac-{arch}" /
+ "DAVE.app" / "Contents" / "Resources"
+ )
+ elif self.platform_name == "windows":
+ resources_dir = self.project_root / "build" / f"win-{arch}" / "resources"
+ else:
+ raise ValueError(f"Unknown platform: {self.platform_name}")
+
+ if not resources_dir.exists():
+ raise FileNotFoundError(f"Resources directory not found: {resources_dir}")
+
+ # Copy static resources
+ static_src = self.project_root / "src" / "main" / "resources" / "static"
+ static_dst = resources_dir / "static"
+ if static_src.exists():
+ shutil.copytree(static_src, static_dst, dirs_exist_ok=True)
+ print(f"Copied static resources to: {static_dst}")
+
+ # Copy templates
+ templates_src = self.project_root / "src" / "main" / "resources" / "templates"
+ templates_dst = resources_dir / "templates"
+ if templates_src.exists():
+ shutil.copytree(templates_src, templates_dst, dirs_exist_ok=True)
+ print(f"Copied templates to: {templates_dst}")
+
+ # Copy bash scripts
+ bash_src = self.project_root / "src" / "main" / "resources" / "bash"
+ bash_dst = resources_dir / "bash"
+ if bash_src.exists():
+ shutil.copytree(bash_src, bash_dst, dirs_exist_ok=True)
+ print(f"Copied bash scripts to: {bash_dst}")
+
+ # Create version files
+ self._create_version_files(resources_dir)
+
+ def _create_version_files(self, resources_dir: Path) -> None:
+ """Create version tracking files"""
+ # Create version.txt
+ version_txt = resources_dir / "version.txt"
+ version_txt.write_text(self.version)
+ print(f"Created: {version_txt}")
+
+ # Create version.js
+ version_js = resources_dir / "static" / "scripts" / "version.js"
+ version_js_content = f'var VERSION = "{self.version}";\n'
+ version_js.write_text(version_js_content)
+ print(f"Created: {version_js}")
+
+ def create_distribution(self) -> Path:
+ """Create distribution package"""
+ print("Creating distribution package...")
+
+ # Ensure dist directory exists
+ self.dist_dir.mkdir(exist_ok=True)
+
+ # Detect current architecture
+ import platform
+ arch = platform.machine().lower()
+ if arch == 'aarch64':
+ arch = 'arm64'
+ elif arch in ['x86_64', 'amd64']:
+ arch = 'x64'
+
+ # Determine build output directory (using actual electron-builder output structure)
+ if self.platform_name == "linux":
+ build_output = self.project_root / "build" / f"linux-{arch}"
+ archive_name = f"DAVE-{self.version}-linux-{arch}.zip"
+ elif self.platform_name == "macos":
+ build_output = self.project_root / "build" / f"mac-{arch}"
+ archive_name = f"DAVE-{self.version}-darwin-{arch}.zip"
+ elif self.platform_name == "windows":
+ build_output = self.project_root / "build" / f"win-{arch}"
+ archive_name = f"DAVE-{self.version}-win32-{arch}.zip"
+ else:
+ raise ValueError(f"Unknown platform: {self.platform_name}")
+
+ if not build_output.exists():
+ raise FileNotFoundError(f"Build output not found: {build_output}")
+
+ # Create zip archive
+ archive_path = self.dist_dir / archive_name
+ print(f"Creating archive: {archive_path}")
+
+ with zipfile.ZipFile(archive_path, "w", zipfile.ZIP_DEFLATED) as zf:
+ for root, dirs, files in os.walk(build_output):
+ for file in files:
+ file_path = Path(root) / file
+ arc_name = file_path.relative_to(build_output.parent)
+ zf.write(file_path, arc_name)
+
+ print(f"Distribution package created: {archive_path}")
+ print(f"Size: {archive_path.stat().st_size / 1024 / 1024:.2f} MB")
+
+ return archive_path
+
+ def build_python(self) -> None:
+ """Build Python components (future: wheel/executable)"""
+ print("Building Python components...")
+
+ # For now, we rely on Pixi environment
+ # In the future, this could:
+ # - Build a Python wheel
+ # - Create a standalone executable with PyInstaller
+ # - Bundle Python runtime
+
+ print("Python build complete (using Pixi environment)")
+
+ def run_tests(self) -> None:
+ """Run test suite"""
+ print("Running tests...")
+
+ # Run Python tests
+ print("\nRunning Python tests...")
+ self._run_command(["pixi", "run", "test"])
+
+ # Run E2E tests using pixi
+ print("\nRunning Electron E2E tests...")
+ self._run_command(["pixi", "run", "test-e2e"])
+
+ def full_build(self, use_forge: bool = True) -> Path:
+ """Execute full build pipeline"""
+ print(f"Starting full build for {self.platform_name}")
+ print(f"Version: {self.version}")
+ print(f"Build system: {'Electron Forge' if use_forge else 'Electron Builder'}")
+ print("-" * 60)
+
+ try:
+ # Install dependencies
+ self.install_dependencies()
+
+ # Build components
+ self.build_python()
+ self.build_electron(use_forge=use_forge)
+
+ if not use_forge:
+ # Electron Builder handles resource copying automatically via package.json
+ # Create distribution from build output
+ dist_path = self.create_distribution()
+ else:
+ # For Forge, return the output directory
+ dist_path = self.electron_dir / "out"
+
+ print("\n" + "=" * 60)
+ print(f"Build completed successfully!")
+ print(f"Output: {dist_path}")
+ print("=" * 60)
+
+ return dist_path
+
+ except Exception as e:
+ print(f"\nBuild failed: {e}", file=sys.stderr)
+ raise
+
+
+def main():
+ """Main entry point"""
+ parser = argparse.ArgumentParser(
+ description="Build DAVE application",
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ epilog="""
+Examples:
+ python build.py # Full build for current platform (uses Forge)
+ python build.py --platform linux # Build for specific platform
+ python build.py --legacy # Use legacy Electron Builder instead of Forge
+ python build.py --clean # Clean build artifacts
+ python build.py --test # Run tests only
+ """
+ )
+
+ parser.add_argument(
+ "--platform",
+ choices=["linux", "macos", "windows"],
+ help="Target platform (default: auto-detect)"
+ )
+ parser.add_argument(
+ "--forge",
+ action="store_true",
+ default=True,
+ help="Use Electron Forge (default: True)"
+ )
+ parser.add_argument(
+ "--legacy",
+ action="store_true",
+ help="Use legacy Electron Builder instead of Forge"
+ )
+ parser.add_argument(
+ "--clean",
+ action="store_true",
+ help="Clean build artifacts"
+ )
+ parser.add_argument(
+ "--test",
+ action="store_true",
+ help="Run tests only"
+ )
+ parser.add_argument(
+ "--no-tests",
+ action="store_true",
+ help="Skip running tests during build"
+ )
+
+ args = parser.parse_args()
+
+ # Create builder
+ builder = DaveBuilder(platform_name=args.platform)
+
+ try:
+ if args.clean:
+ builder.clean()
+ elif args.test:
+ builder.run_tests()
+ else:
+ # Run tests first unless skipped
+ if not args.no_tests:
+ builder.run_tests()
+
+ # Execute full build
+ use_forge = not args.legacy # Use Forge unless --legacy is specified
+ builder.full_build(use_forge=use_forge)
+
+ except KeyboardInterrupt:
+ print("\nBuild interrupted by user")
+ sys.exit(1)
+ except Exception as e:
+ print(f"\nBuild error: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/contributors.txt b/contributors.txt
index d2a0e27d..efeafa43 100644
--- a/contributors.txt
+++ b/contributors.txt
@@ -1,3 +1,4 @@
Danish Sodhi dsodhi95@gmail.com
Paul Balm paul@timelabtechnologies.com
Ricardo Valles rvallesblanco@gmail.com
+Kartik Mandar kartik4321mandar@gmail.com
\ No newline at end of file
diff --git a/data/monol_testA.evt b/data/monol_testA.evt
new file mode 100644
index 00000000..1578a4f8
--- /dev/null
+++ b/data/monol_testA.evt
@@ -0,0 +1,22 @@
+SIMPLE = T / conforms to FITS standard BITPIX = 8 / array data type NAXIS = 0 / number of array dimensions EXTEND = T OBSERVER= 'Edwige Bubble' TELESCOP= 'NuSTAR ' / Telescope (mission) name INSTRUME= 'FPMA ' / Instrument name END XTENSION= 'BINTABLE' / binary table extension BITPIX = 8 / array data type NAXIS = 2 / number of array dimensions NAXIS1 = 12 / length of dimension 1 NAXIS2 = 1000 / length of dimension 2 PCOUNT = 0 / number of group parameters GCOUNT = 1 / number of groups TFIELDS = 2 / number of table fields TTYPE1 = 'TIME ' TFORM1 = '1D ' TTYPE2 = 'PI ' TFORM2 = '1J ' EXTNAME = 'EVENTS ' / extension name OBSERVER= 'Edwige Bubble' TELESCOP= 'NuSTAR ' / Telescope (mission) name INSTRUME= 'FPMA ' / Instrument name OBS_ID = '00000000001' / Observation ID TARG_ID = 0 / Target ID OBJECT = 'Fake X-1' / Name of observed object RA_OBJ = 0.0 / [deg] R.A. Object DEC_OBJ = 0.0 / [deg] Dec Object RA_NOM = 0.0 / Right Ascension used for barycenter correctionsDEC_NOM = 0.0 / Declination used for barycenter corrections RA_PNT = 0.0 / [deg] RA pointing DEC_PNT = 0.0 / [deg] Dec pointing PA_PNT = 0.0 / [deg] Position angle (roll) EQUINOX = 2000.0 / Equinox of celestial coord system RADECSYS= 'FK5 ' / Coordinate Reference System TASSIGN = 'SATELLITE' / Time assigned by onboard clock TIMESYS = 'TDB ' / All times in this file are TDB MJDREFI = 55197 / TDB time reference; Modified Julian Day (int) MJDREFF = 0.00076601852 / TDB time reference; Modified Julian Day (frac) TIMEREF = 'SOLARSYSTEM' / Times are pathlength-corrected to barycenter CLOCKAPP= F / TRUE if timestamps corrected by gnd sware TIMEUNIT= 's ' / unit for time keywords TSTART = 80000000.0 / Elapsed seconds since MJDREF at start of file TSTOP = 80001025.0 / Elapsed seconds since MJDREF at end of file LIVETIME= 1025.0 / On-source time TIMEZERO= 0.0 / Time Zero COMMENT FITS (Flexible Image Transport System) format is defined in 'Astronomy aCOMMENT nd Astrophysics', volume 376, page 359; bibcode: 2001A&A...376..359H MJDREFF = 7.6601852000000E-04 MJDREFI = 55197 COMMENT MJDREFI+MJDREFF = epoch of Jan 1, 2010, in TT time system. PLEPHEM = 'JPL-DE200' HISTORY File modified by user 'meo' with fv on 2021-01-22T10:29:45 HISTORY File modified by user 'meo' with fv on 2021-05-04T18:37:20 END A : A02 A#k6 A(a A
AQV 4A_Vt A A > kA A&