Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 56 additions & 20 deletions PYME/Acquire/acquire_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,10 +203,16 @@ def __init__(self, port, bind_addr=None):
webui.set_server(self)

self._main_page = webui.load_template('PYMEAcquire.html')
self._login_page = webui.load_template('login.html')

@webframework.register_endpoint('/do_login')
def do_login(self, email, password, on_success='/'):
"""
JSON-based login endpoint that returns authentication token
Client is responsible for setting the cookie
"""
from PYME.util import authenticate
import json

try:
auth = authenticate.get_token(email, password)
Expand All @@ -215,28 +221,58 @@ def do_login(self, email, password, on_success='/'):
auth = None

if auth:
return webframework.HTTPRedirectResponse(on_success, headers=[('Set-Cookie', 'auth=%s; path=/; HttpOnly' % auth)])
return json.dumps({
'success': True,
'token': auth,
'user': email
})
else:
return webframework.HTTPRedirectResponse('/login?reason="failure"&on_success="%s"'%on_success, headers=[('Set-Cookie', 'auth=; path=/; HttpOnly; expires=Thu, 01 Jan 1970 00:00:00 GMT')])

@webframework.register_endpoint('/login', mimetype='text/html')
def login(self, reason='', on_success='/'):
from jinja2 import Template

return Template(webui.load_template('login.html')).render(reason=reason, on_success=on_success)

return json.dumps({
'success': False,
'error': 'Invalid email or password'
})

@webframework.register_endpoint('/logout')
def logout(self, on_success='/'):
return webframework.HTTPRedirectResponse(on_success, headers=[
('Set-Cookie', 'auth=; path=/; HttpOnly; expires=Thu, 01 Jan 1970 00:00:00 GMT')])

@webframework.register_endpoint('/', mimetype='text/html', authenticate=True)
def main_page(self, authenticated_as=None):
#return self._main_page
from jinja2 import Template

print('authenticated_as=', authenticated_as)
return Template(webui.load_template('PYMEAcquire.html')).render(authenticated_as=authenticated_as)
def logout(self):
"""
JSON-based logout endpoint
Client is responsible for clearing the cookie
"""
import json
return json.dumps({
'success': True,
'message': 'Logged out successfully'
})

@webframework.register_endpoint('/api/user', authenticate=True)
def get_user_info(self, authenticated_as=None):
"""
Return current user info if authenticated
"""
import json
if authenticated_as:
return json.dumps({
'authenticated': True,
'user': authenticated_as
})
else:
return json.dumps({
'authenticated': False
})

@webframework.register_endpoint('/login.html', mimetype='text/html')
def login_page(self):
"""
Serve static login HTML
"""
return self._login_page

@webframework.register_endpoint('/', mimetype='text/html')
def main_page(self):
"""
Serve static HTML - authentication is now handled client-side
"""
return webui.load_template('PYMEAcquire.html')

def run(self):
self._poll_thread = threading.Thread(target=self.main_loop)
Expand Down
181 changes: 181 additions & 0 deletions PYME/Acquire/webui/app/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
# PYME Acquire Web UI - Modern Vue.js App

A modern, single-page Vue.js application for controlling PYME Acquire microscopy system via a web interface.

## Features

- **Modern Vue 3** with Composition API
- **Single-file components** (.vue files)
- **Pinia** for state management
- **Vue Router** for navigation
- **Vite** for fast development and optimized builds
- **Real-time updates** via long-polling
- **Responsive design** with dark theme
- **Client-side authentication**

## Project Structure

```
app/
├── index.html # Main HTML entry point
├── package.json # Dependencies and scripts
├── vite.config.js # Vite configuration
└── src/
├── main.js # Application entry point
├── App.vue # Root component
├── style.css # Global styles
├── router/
│ └── index.js # Router configuration
├── stores/ # Pinia stores (state management)
│ ├── auth.js # Authentication state
│ ├── scope.js # Microscope state
│ ├── spooler.js # Data acquisition state
│ └── stack.js # Stack settings state
├── api/
│ └── index.js # API client functions
├── views/ # Page-level components
│ ├── LoginView.vue
│ └── MainView.vue
└── components/ # Reusable components
├── CameraDisplay.vue
├── DisplayControls.vue
├── HardwareControls.vue
├── AcquisitionControls.vue
├── LaserControl.vue
├── PositionControl.vue
├── StackSettings.vue
└── tabs/
├── AcquisitionTab.vue
├── StateTab.vue
└── ConsoleTab.vue
```

## Development

### Prerequisites

- Node.js 18+ and npm
- Python PYME Acquire server running on port 9797

### Installation

```bash
cd PYME/Acquire/webui/app
npm install
```

### Development Server

```bash
npm run dev
```

This starts the Vite development server on http://localhost:3000 with hot-module replacement. API requests are proxied to the Python server at http://localhost:9797.

### Building for Production

```bash
npm run build
```

This creates an optimized production build in `../dist/` directory.

### Preview Production Build

```bash
npm run preview
```

## Architecture

### State Management

The app uses **Pinia** stores for managing application state:

- **authStore**: User authentication and session management
- **scopeStore**: Microscope hardware state (cameras, lasers, positioning)
- **spoolerStore**: Data acquisition and spooling state
- **stackStore**: Z-stack acquisition settings

### API Communication

All server communication is handled through the `api/index.js` module which provides:

- RESTful API calls for configuration and control
- Long-polling for real-time state updates
- Cookie-based authentication

### Components

Components are organized by function:

- **Views**: Top-level page components (Login, Main)
- **Controls**: Hardware control components (Camera, Lasers, Positioning)
- **Tabs**: Tab content components (Acquisition, State, Console)
- **Shared**: Reusable UI components

### Routing

Vue Router handles navigation with authentication guards:

- `/login` - Login page
- `/` - Main application (requires authentication)

## Integration with Python Server

The app expects the Python PYME Acquire server to provide these endpoints:

### Authentication
- `GET /do_login?email=...&password=...` - Login and get token
- `GET /logout` - Logout
- `GET /api/user` - Get current user info

### Scope State
- `GET /get_scope_state` - Get current state
- `POST /update_scope_state` - Update state
- `GET /scope_state_longpoll` - Long-poll for state updates

### Camera
- `GET /get_frame_pzf` - Get camera frame (PZF format)

### Spooler
- `GET /spool_controller/info` - Get spooler info
- `POST /spool_controller/settings` - Update settings
- `GET /spool_controller/start_spooling` - Start acquisition
- `GET /spool_controller/stop_spooling` - Stop acquisition
- `GET /spool_controller/info_longpoll` - Long-poll for updates

### Stack Settings
- `GET /stack_settings/settings` - Get settings
- `POST /stack_settings/update` - Update settings
- `GET /stack_settings/settings_longpoll` - Long-poll for updates

## Deployment

To serve the built app from the Python server, the dist files should be served as static files. Update the Python server to serve the built `index.html` at the root path and static assets from the `dist/assets/` directory.

## Migration from Legacy App

This modern Vue app replaces the legacy jQuery/Vue 2 app with:

- ✅ Modern build tooling (Vite instead of manual scripts)
- ✅ TypeScript-ready architecture
- ✅ Proper component organization
- ✅ State management with Pinia
- ✅ Better developer experience with HMR
- ✅ Optimized production builds
- ✅ Maintainable codebase

## TODO

- [ ] Port PZF decoder for camera display
- [ ] Add protocol file selection UI
- [ ] Add simulation controls
- [ ] Implement camera frame autoscaling
- [ ] Add error boundaries and loading states
- [ ] Add unit tests
- [ ] Add E2E tests

## License

Same as PYME project (GPL v3)
Loading
Loading