Enhance Workflow Execution with Iframe Support and Error Notification
- Introduced iframe mode detection to streamline the user experience when embedded in other applications. - Added functionality to update the URL upon process completion, allowing for better integration with parent applications. - Implemented error notification to the parent iframe, ensuring that any issues during workflow execution are communicated effectively. - Enhanced UI responsiveness by adjusting styles based on iframe mode, improving overall usability and visual consistency. - Updated process completion and error handling logic to support seamless multi-process workflows.
This commit is contained in:
parent
1448aef0ed
commit
e4b1c7e444
725
content/documentation/08-iframe-integration.md
Normal file
725
content/documentation/08-iframe-integration.md
Normal file
@ -0,0 +1,725 @@
|
||||
# Iframe Integration Guide
|
||||
|
||||
This guide explains how to integrate Corrad workflow forms into external applications using iframes. This enables seamless embedding of business processes into customer portals, external websites, or any web application.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Overview](#overview)
|
||||
- [Basic Integration](#basic-integration)
|
||||
- [Advanced Configuration](#advanced-configuration)
|
||||
- [Multi-Process Workflows](#multi-process-workflows)
|
||||
- [Error Handling](#error-handling)
|
||||
- [Security Considerations](#security-considerations)
|
||||
- [Examples](#examples)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
|
||||
## Overview
|
||||
|
||||
Corrad provides a powerful iframe integration system that allows you to embed workflow forms into external applications. The system supports:
|
||||
|
||||
- **Clean UI Mode**: Hide all debug information and UI chrome
|
||||
- **Seamless Multi-Process**: Chain multiple workflows together
|
||||
- **Real-time Communication**: Parent-child message passing
|
||||
- **Error Handling**: Graceful error management
|
||||
- **Progress Tracking**: Monitor workflow completion
|
||||
|
||||
## Basic Integration
|
||||
|
||||
### Simple Iframe Embedding
|
||||
|
||||
```html
|
||||
<!-- Basic iframe integration -->
|
||||
<iframe
|
||||
src="/workflow/your-process-id?debug=false"
|
||||
width="100%"
|
||||
height="600px"
|
||||
style="border: none;">
|
||||
</iframe>
|
||||
```
|
||||
|
||||
### URL Parameters
|
||||
|
||||
| Parameter | Description | Example |
|
||||
|-----------|-------------|---------|
|
||||
| `debug=false` | Enable iframe mode (hide UI chrome) | `?debug=false` |
|
||||
| `hideComplete=true` | Hide completion message | `?hideComplete=true` |
|
||||
| `theme=dark` | Apply custom theme (if supported) | `?theme=dark` |
|
||||
|
||||
### Complete Example
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Workflow Integration</title>
|
||||
<style>
|
||||
.workflow-container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
.workflow-iframe {
|
||||
width: 100%;
|
||||
height: 600px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="workflow-container">
|
||||
<h1>Complete Your Application</h1>
|
||||
<iframe
|
||||
id="workflow-iframe"
|
||||
src="/workflow/application-form?debug=false&hideComplete=true"
|
||||
class="workflow-iframe">
|
||||
</iframe>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Listen for workflow completion
|
||||
window.addEventListener('message', (event) => {
|
||||
if (event.data.type === 'workflow-complete') {
|
||||
console.log('Workflow completed:', event.data);
|
||||
// Handle completion
|
||||
showSuccessMessage('Application submitted successfully!');
|
||||
} else if (event.data.type === 'workflow-error') {
|
||||
console.error('Workflow error:', event.data);
|
||||
showErrorMessage(event.data.error);
|
||||
}
|
||||
});
|
||||
|
||||
function showSuccessMessage(message) {
|
||||
alert(message);
|
||||
}
|
||||
|
||||
function showErrorMessage(error) {
|
||||
alert('Error: ' + error);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## Advanced Configuration
|
||||
|
||||
### Message Event Structure
|
||||
|
||||
The workflow sends the following message types to the parent iframe:
|
||||
|
||||
#### Completion Message
|
||||
```javascript
|
||||
{
|
||||
type: 'workflow-complete',
|
||||
processId: 'application-form',
|
||||
processName: 'Application Form',
|
||||
hideCompletionMessage: true,
|
||||
timestamp: '2024-01-15T10:30:00.000Z'
|
||||
}
|
||||
```
|
||||
|
||||
#### Error Message
|
||||
```javascript
|
||||
{
|
||||
type: 'workflow-error',
|
||||
processId: 'application-form',
|
||||
processName: 'Application Form',
|
||||
error: 'API call failed: Network error',
|
||||
timestamp: '2024-01-15T10:30:00.000Z'
|
||||
}
|
||||
```
|
||||
|
||||
### Advanced Integration Example
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Advanced Workflow Integration</title>
|
||||
<style>
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
.header {
|
||||
background: #f8f9fa;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.progress-bar {
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
background: #e9ecef;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: #007bff;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
.workflow-iframe {
|
||||
width: 100%;
|
||||
height: 600px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.status {
|
||||
margin-top: 10px;
|
||||
font-size: 14px;
|
||||
color: #6c757d;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>Employee Onboarding</h1>
|
||||
<div class="progress-bar">
|
||||
<div id="progress-fill" class="progress-fill" style="width: 0%"></div>
|
||||
</div>
|
||||
<div id="status" class="status">Starting process...</div>
|
||||
</div>
|
||||
|
||||
<iframe
|
||||
id="workflow-iframe"
|
||||
src="/workflow/personal-info?debug=false&hideComplete=true"
|
||||
class="workflow-iframe">
|
||||
</iframe>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let currentStep = 0;
|
||||
const totalSteps = 5;
|
||||
|
||||
// Workflow steps configuration
|
||||
const workflowSteps = [
|
||||
{ id: 'personal-info', name: 'Personal Information' },
|
||||
{ id: 'employment', name: 'Employment Details' },
|
||||
{ id: 'documents', name: 'Document Upload' },
|
||||
{ id: 'benefits', name: 'Benefits Selection' },
|
||||
{ id: 'final', name: 'Final Review' }
|
||||
];
|
||||
|
||||
// Listen for workflow messages
|
||||
window.addEventListener('message', (event) => {
|
||||
if (event.data.type === 'workflow-complete') {
|
||||
console.log('Workflow step completed:', event.data);
|
||||
|
||||
if (event.data.hideCompletionMessage) {
|
||||
// Auto-advance to next step
|
||||
setTimeout(() => {
|
||||
loadNextStep();
|
||||
}, 100);
|
||||
}
|
||||
} else if (event.data.type === 'workflow-error') {
|
||||
console.error('Workflow error:', event.data);
|
||||
showError(event.data.error);
|
||||
}
|
||||
});
|
||||
|
||||
function loadNextStep() {
|
||||
currentStep++;
|
||||
|
||||
if (currentStep >= workflowSteps.length) {
|
||||
// All steps complete
|
||||
showFinalSuccess();
|
||||
return;
|
||||
}
|
||||
|
||||
const step = workflowSteps[currentStep];
|
||||
const iframe = document.getElementById('workflow-iframe');
|
||||
|
||||
// Update iframe source
|
||||
iframe.src = `/workflow/${step.id}?debug=false&hideComplete=true`;
|
||||
|
||||
// Update progress
|
||||
updateProgress();
|
||||
}
|
||||
|
||||
function updateProgress() {
|
||||
const progress = ((currentStep + 1) / totalSteps) * 100;
|
||||
document.getElementById('progress-fill').style.width = progress + '%';
|
||||
document.getElementById('status').textContent =
|
||||
`Step ${currentStep + 1} of ${totalSteps}: ${workflowSteps[currentStep].name}`;
|
||||
}
|
||||
|
||||
function showFinalSuccess() {
|
||||
document.getElementById('workflow-iframe').style.display = 'none';
|
||||
document.querySelector('.header').innerHTML = `
|
||||
<h1>🎉 Onboarding Complete!</h1>
|
||||
<p>All required information has been submitted successfully.</p>
|
||||
<button onclick="downloadReport()">Download Report</button>
|
||||
`;
|
||||
}
|
||||
|
||||
function showError(error) {
|
||||
document.getElementById('status').textContent = `Error: ${error}`;
|
||||
document.getElementById('status').style.color = '#dc3545';
|
||||
}
|
||||
|
||||
function downloadReport() {
|
||||
// Implement report download logic
|
||||
alert('Report download functionality would be implemented here');
|
||||
}
|
||||
|
||||
// Initialize progress
|
||||
updateProgress();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## Multi-Process Workflows
|
||||
|
||||
### Seamless Multi-Step Process
|
||||
|
||||
For complex workflows with multiple steps, you can chain processes together seamlessly:
|
||||
|
||||
```javascript
|
||||
// Multi-process workflow configuration
|
||||
const processes = [
|
||||
{ id: 'personal-info', name: 'Personal Information' },
|
||||
{ id: 'employment', name: 'Employment Details' },
|
||||
{ id: 'documents', name: 'Document Upload' },
|
||||
{ id: 'references', name: 'References' },
|
||||
{ id: 'background', name: 'Background Check' },
|
||||
{ id: 'medical', name: 'Medical Information' },
|
||||
{ id: 'emergency', name: 'Emergency Contacts' },
|
||||
{ id: 'benefits', name: 'Benefits Selection' },
|
||||
{ id: 'payroll', name: 'Payroll Information' },
|
||||
{ id: 'final', name: 'Final Review' }
|
||||
];
|
||||
|
||||
let currentProcessIndex = 0;
|
||||
|
||||
function loadProcess(processIndex) {
|
||||
if (processIndex >= processes.length) {
|
||||
// All processes complete
|
||||
showFinalSuccess();
|
||||
return;
|
||||
}
|
||||
|
||||
const process = processes[processIndex];
|
||||
const iframe = document.getElementById('workflow-iframe');
|
||||
|
||||
// Load process with hidden completion message
|
||||
iframe.src = `/workflow/${process.id}?debug=false&hideComplete=true`;
|
||||
currentProcessIndex = processIndex;
|
||||
|
||||
// Update progress indicator
|
||||
updateProgress(processIndex + 1, processes.length);
|
||||
}
|
||||
|
||||
// Listen for completion and auto-advance
|
||||
window.addEventListener('message', (event) => {
|
||||
if (event.data.type === 'workflow-complete') {
|
||||
console.log(`Process ${event.data.processName} completed`);
|
||||
|
||||
if (event.data.hideCompletionMessage) {
|
||||
// Seamlessly move to next process
|
||||
setTimeout(() => {
|
||||
loadProcess(currentProcessIndex + 1);
|
||||
}, 100);
|
||||
}
|
||||
} else if (event.data.type === 'workflow-error') {
|
||||
console.error('Process failed:', event.data.error);
|
||||
showErrorMessage(event.data.error);
|
||||
}
|
||||
});
|
||||
|
||||
// Start the first process
|
||||
loadProcess(0);
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Comprehensive Error Management
|
||||
|
||||
```javascript
|
||||
// Enhanced error handling
|
||||
window.addEventListener('message', (event) => {
|
||||
if (event.data.type === 'workflow-complete') {
|
||||
handleWorkflowComplete(event.data);
|
||||
} else if (event.data.type === 'workflow-error') {
|
||||
handleWorkflowError(event.data);
|
||||
}
|
||||
});
|
||||
|
||||
function handleWorkflowComplete(data) {
|
||||
console.log('Workflow completed:', data);
|
||||
|
||||
// Store completion data
|
||||
localStorage.setItem('workflow-completion', JSON.stringify(data));
|
||||
|
||||
// Update UI
|
||||
updateCompletionUI(data);
|
||||
|
||||
// Auto-advance if configured
|
||||
if (data.hideCompletionMessage) {
|
||||
setTimeout(() => {
|
||||
loadNextProcess();
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
function handleWorkflowError(data) {
|
||||
console.error('Workflow error:', data);
|
||||
|
||||
// Log error for debugging
|
||||
console.error('Error details:', {
|
||||
processId: data.processId,
|
||||
processName: data.processName,
|
||||
error: data.error,
|
||||
timestamp: data.timestamp
|
||||
});
|
||||
|
||||
// Show user-friendly error message
|
||||
showErrorMessage(data.error);
|
||||
|
||||
// Optionally retry or allow manual restart
|
||||
showRetryOptions();
|
||||
}
|
||||
|
||||
function showErrorMessage(error) {
|
||||
// Create error notification
|
||||
const errorDiv = document.createElement('div');
|
||||
errorDiv.className = 'error-notification';
|
||||
errorDiv.innerHTML = `
|
||||
<div class="error-content">
|
||||
<h3>⚠️ An error occurred</h3>
|
||||
<p>${error}</p>
|
||||
<button onclick="retryCurrentProcess()">Retry</button>
|
||||
<button onclick="skipCurrentProcess()">Skip</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(errorDiv);
|
||||
}
|
||||
|
||||
function retryCurrentProcess() {
|
||||
const iframe = document.getElementById('workflow-iframe');
|
||||
iframe.src = iframe.src; // Reload current process
|
||||
}
|
||||
|
||||
function skipCurrentProcess() {
|
||||
// Move to next process
|
||||
loadNextProcess();
|
||||
}
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Cross-Origin Communication
|
||||
|
||||
When integrating iframes, consider these security aspects:
|
||||
|
||||
1. **Origin Validation**: Verify message origin
|
||||
2. **Content Security Policy**: Configure appropriate CSP headers
|
||||
3. **Sandbox Attributes**: Use iframe sandbox for additional security
|
||||
|
||||
```html
|
||||
<!-- Secure iframe configuration -->
|
||||
<iframe
|
||||
src="/workflow/process-id?debug=false"
|
||||
sandbox="allow-scripts allow-same-origin allow-forms"
|
||||
referrerpolicy="no-referrer">
|
||||
</iframe>
|
||||
```
|
||||
|
||||
### Message Validation
|
||||
|
||||
```javascript
|
||||
// Validate message origin
|
||||
window.addEventListener('message', (event) => {
|
||||
// Verify origin (replace with your domain)
|
||||
if (event.origin !== 'https://your-workflow-domain.com') {
|
||||
console.warn('Message from unauthorized origin:', event.origin);
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate message structure
|
||||
if (!event.data || typeof event.data.type !== 'string') {
|
||||
console.warn('Invalid message structure:', event.data);
|
||||
return;
|
||||
}
|
||||
|
||||
// Process valid message
|
||||
handleWorkflowMessage(event.data);
|
||||
});
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Customer Portal Integration
|
||||
|
||||
```html
|
||||
<!-- Customer portal with workflow integration -->
|
||||
<div class="customer-portal">
|
||||
<header>
|
||||
<h1>Welcome, {{ customer.name }}</h1>
|
||||
<nav>
|
||||
<a href="#dashboard">Dashboard</a>
|
||||
<a href="#applications">Applications</a>
|
||||
<a href="#documents">Documents</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<div class="application-section">
|
||||
<h2>Complete Your Application</h2>
|
||||
<div class="progress-indicator">
|
||||
<span id="progress-text">Step 1 of 5</span>
|
||||
<div class="progress-bar">
|
||||
<div id="progress-fill"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<iframe
|
||||
id="application-iframe"
|
||||
src="/workflow/customer-application?debug=false&hideComplete=true"
|
||||
style="width: 100%; height: 600px; border: none;">
|
||||
</iframe>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Customer portal integration
|
||||
const applicationSteps = [
|
||||
'personal-info',
|
||||
'employment-history',
|
||||
'document-upload',
|
||||
'references',
|
||||
'final-submission'
|
||||
];
|
||||
|
||||
let currentStep = 0;
|
||||
|
||||
window.addEventListener('message', (event) => {
|
||||
if (event.data.type === 'workflow-complete') {
|
||||
currentStep++;
|
||||
updateProgress();
|
||||
|
||||
if (currentStep < applicationSteps.length) {
|
||||
// Load next step
|
||||
const iframe = document.getElementById('application-iframe');
|
||||
iframe.src = `/workflow/${applicationSteps[currentStep]}?debug=false&hideComplete=true`;
|
||||
} else {
|
||||
// Application complete
|
||||
showApplicationComplete();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function updateProgress() {
|
||||
const progress = ((currentStep + 1) / applicationSteps.length) * 100;
|
||||
document.getElementById('progress-fill').style.width = progress + '%';
|
||||
document.getElementById('progress-text').textContent = `Step ${currentStep + 1} of ${applicationSteps.length}`;
|
||||
}
|
||||
|
||||
function showApplicationComplete() {
|
||||
document.getElementById('application-iframe').style.display = 'none';
|
||||
document.querySelector('.application-section').innerHTML = `
|
||||
<div class="completion-message">
|
||||
<h2>🎉 Application Submitted!</h2>
|
||||
<p>Your application has been successfully submitted. We'll review it and contact you soon.</p>
|
||||
<button onclick="downloadApplication()">Download Application</button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### Employee Onboarding System
|
||||
|
||||
```html
|
||||
<!-- Employee onboarding with multiple workflows -->
|
||||
<div class="onboarding-system">
|
||||
<div class="onboarding-header">
|
||||
<h1>Employee Onboarding</h1>
|
||||
<div class="step-indicator">
|
||||
<div class="step active" data-step="1">Personal Info</div>
|
||||
<div class="step" data-step="2">Employment</div>
|
||||
<div class="step" data-step="3">Documents</div>
|
||||
<div class="step" data-step="4">Benefits</div>
|
||||
<div class="step" data-step="5">Final</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="workflow-container">
|
||||
<iframe
|
||||
id="onboarding-iframe"
|
||||
src="/workflow/personal-info?debug=false&hideComplete=true"
|
||||
style="width: 100%; height: 700px; border: none;">
|
||||
</iframe>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Employee onboarding integration
|
||||
const onboardingSteps = [
|
||||
{ id: 'personal-info', name: 'Personal Information' },
|
||||
{ id: 'employment', name: 'Employment Details' },
|
||||
{ id: 'documents', name: 'Document Upload' },
|
||||
{ id: 'benefits', name: 'Benefits Selection' },
|
||||
{ id: 'final', name: 'Final Review' }
|
||||
];
|
||||
|
||||
let currentStepIndex = 0;
|
||||
|
||||
window.addEventListener('message', (event) => {
|
||||
if (event.data.type === 'workflow-complete') {
|
||||
console.log(`Step ${event.data.processName} completed`);
|
||||
|
||||
if (event.data.hideCompletionMessage) {
|
||||
// Auto-advance to next step
|
||||
setTimeout(() => {
|
||||
loadNextStep();
|
||||
}, 200);
|
||||
}
|
||||
} else if (event.data.type === 'workflow-error') {
|
||||
handleOnboardingError(event.data);
|
||||
}
|
||||
});
|
||||
|
||||
function loadNextStep() {
|
||||
currentStepIndex++;
|
||||
|
||||
if (currentStepIndex >= onboardingSteps.length) {
|
||||
// Onboarding complete
|
||||
showOnboardingComplete();
|
||||
return;
|
||||
}
|
||||
|
||||
const step = onboardingSteps[currentStepIndex];
|
||||
const iframe = document.getElementById('onboarding-iframe');
|
||||
|
||||
// Update iframe
|
||||
iframe.src = `/workflow/${step.id}?debug=false&hideComplete=true`;
|
||||
|
||||
// Update step indicator
|
||||
updateStepIndicator(currentStepIndex + 1);
|
||||
}
|
||||
|
||||
function updateStepIndicator(activeStep) {
|
||||
document.querySelectorAll('.step').forEach((step, index) => {
|
||||
if (index + 1 <= activeStep) {
|
||||
step.classList.add('active');
|
||||
} else {
|
||||
step.classList.remove('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function showOnboardingComplete() {
|
||||
document.getElementById('onboarding-iframe').style.display = 'none';
|
||||
document.querySelector('.workflow-container').innerHTML = `
|
||||
<div class="completion-message">
|
||||
<h2>🎉 Onboarding Complete!</h2>
|
||||
<p>Welcome to the team! Your onboarding process has been completed successfully.</p>
|
||||
<div class="next-steps">
|
||||
<h3>Next Steps:</h3>
|
||||
<ul>
|
||||
<li>Check your email for login credentials</li>
|
||||
<li>Complete your first-day orientation</li>
|
||||
<li>Meet with your manager</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function handleOnboardingError(data) {
|
||||
console.error('Onboarding error:', data);
|
||||
// Show error message and retry options
|
||||
showErrorModal(data.error);
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues and Solutions
|
||||
|
||||
#### 1. Iframe Not Loading
|
||||
|
||||
**Problem**: Iframe shows blank or doesn't load
|
||||
**Solution**: Check URL parameters and CORS settings
|
||||
|
||||
```javascript
|
||||
// Debug iframe loading
|
||||
const iframe = document.getElementById('workflow-iframe');
|
||||
iframe.onload = () => {
|
||||
console.log('Iframe loaded successfully');
|
||||
};
|
||||
iframe.onerror = () => {
|
||||
console.error('Iframe failed to load');
|
||||
};
|
||||
```
|
||||
|
||||
#### 2. Messages Not Received
|
||||
|
||||
**Problem**: Parent iframe not receiving completion messages
|
||||
**Solution**: Verify event listener and origin settings
|
||||
|
||||
```javascript
|
||||
// Debug message reception
|
||||
window.addEventListener('message', (event) => {
|
||||
console.log('Message received:', event.data);
|
||||
// Your message handling logic
|
||||
});
|
||||
```
|
||||
|
||||
#### 3. Cross-Origin Issues
|
||||
|
||||
**Problem**: CORS errors when loading iframe
|
||||
**Solution**: Configure proper CORS headers on the workflow server
|
||||
|
||||
```nginx
|
||||
# Nginx configuration example
|
||||
add_header Access-Control-Allow-Origin "*";
|
||||
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
|
||||
add_header Access-Control-Allow-Headers "Content-Type";
|
||||
```
|
||||
|
||||
#### 4. URL Parameter Issues
|
||||
|
||||
**Problem**: URL parameters not working as expected
|
||||
**Solution**: Verify parameter encoding
|
||||
|
||||
```javascript
|
||||
// Proper URL construction
|
||||
const baseUrl = '/workflow/process-id';
|
||||
const params = new URLSearchParams({
|
||||
debug: 'false',
|
||||
hideComplete: 'true'
|
||||
});
|
||||
const fullUrl = `${baseUrl}?${params.toString()}`;
|
||||
```
|
||||
|
||||
### Debug Checklist
|
||||
|
||||
- [ ] Iframe source URL is correct
|
||||
- [ ] URL parameters are properly encoded
|
||||
- [ ] Event listener is attached to window
|
||||
- [ ] Origin validation is configured
|
||||
- [ ] CORS headers are set on server
|
||||
- [ ] Console shows no JavaScript errors
|
||||
- [ ] Network tab shows successful iframe load
|
||||
|
||||
### Support
|
||||
|
||||
For additional support with iframe integration:
|
||||
|
||||
1. Check the browser console for error messages
|
||||
2. Verify network connectivity to the workflow server
|
||||
3. Test with different browsers
|
||||
4. Review server logs for any backend issues
|
||||
5. Contact support with specific error details
|
||||
|
||||
---
|
||||
|
||||
This documentation provides comprehensive guidance for integrating Corrad workflows into external applications. The iframe system is designed to be flexible, secure, and easy to implement while providing a seamless user experience.
|
@ -3973,7 +3973,7 @@ const sendToBack = () => {
|
||||
}
|
||||
|
||||
/* Ensure all text elements in nodes inherit the custom text color */
|
||||
:deep(.custom-node *:not(.icon)) {
|
||||
:deep(.custom-node *:not(.icon):not([class^="validation-"])) {
|
||||
color: inherit !important;
|
||||
}
|
||||
|
||||
|
@ -12,8 +12,6 @@ definePageMeta({
|
||||
title: "Process Execution",
|
||||
description: "Execute and run through a business process workflow",
|
||||
layout: "empty",
|
||||
middleware: ["auth"],
|
||||
requiresAuth: true,
|
||||
});
|
||||
|
||||
// Get route and router
|
||||
@ -23,6 +21,16 @@ const router = useRouter();
|
||||
// Get form builder store for ComponentPreview data sharing
|
||||
const formStore = useFormBuilderStore();
|
||||
|
||||
// Check if we're in iframe/embed mode
|
||||
const isIframeMode = computed(() => {
|
||||
return route.query.debug === 'false';
|
||||
});
|
||||
|
||||
// Check if we should hide completion message (for seamless multi-process workflows)
|
||||
const hideCompletionMessage = computed(() => {
|
||||
return route.query.hideComplete === 'true';
|
||||
});
|
||||
|
||||
// State
|
||||
const loading = ref(true);
|
||||
const process = ref(null);
|
||||
@ -68,6 +76,13 @@ const isProcessComplete = computed(() => {
|
||||
return currentNode.value?.type === 'end' || currentStep.value >= (workflowData.value?.nodes?.length || 0);
|
||||
});
|
||||
|
||||
// Watch for process completion and update URL
|
||||
watch(isProcessComplete, (newValue) => {
|
||||
if (newValue) {
|
||||
updateUrlOnComplete();
|
||||
}
|
||||
});
|
||||
|
||||
// Load process data
|
||||
const loadProcess = async () => {
|
||||
try {
|
||||
@ -96,10 +111,12 @@ const loadProcess = async () => {
|
||||
|
||||
} else {
|
||||
error.value = response.message || 'Failed to load process';
|
||||
notifyParentOfError(error.value);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[Workflow] Error loading process:', err);
|
||||
error.value = 'Failed to load process data';
|
||||
notifyParentOfError(error.value);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
@ -128,6 +145,7 @@ const startProcessExecution = async () => {
|
||||
} catch (err) {
|
||||
console.error('[Workflow] Error starting process execution:', err);
|
||||
error.value = 'Failed to start process execution';
|
||||
notifyParentOfError(error.value);
|
||||
}
|
||||
};
|
||||
|
||||
@ -637,6 +655,7 @@ const executeCurrentStep = async () => {
|
||||
}
|
||||
} else {
|
||||
error.value = 'API call failed: ' + (err.message || err);
|
||||
notifyParentOfError(error.value);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -675,6 +694,7 @@ const executeCurrentStep = async () => {
|
||||
}
|
||||
} else {
|
||||
error.value = 'API call failed: ' + (err.message || err);
|
||||
notifyParentOfError(error.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -690,6 +710,7 @@ const executeCurrentStep = async () => {
|
||||
} catch (err) {
|
||||
console.error('[Workflow] Error executing script node:', err);
|
||||
error.value = 'Script execution failed: ' + (err.message || err);
|
||||
notifyParentOfError(error.value);
|
||||
}
|
||||
}
|
||||
// Only auto-progress if there's a single outgoing edge
|
||||
@ -811,6 +832,7 @@ const executeCurrentStep = async () => {
|
||||
}
|
||||
} else {
|
||||
error.value = 'Notification failed: ' + (err.message || err);
|
||||
notifyParentOfError(error.value);
|
||||
}
|
||||
}
|
||||
} else if (['decision', 'gateway'].includes(currentNode.value?.type)) {
|
||||
@ -823,6 +845,7 @@ const executeCurrentStep = async () => {
|
||||
} catch (err) {
|
||||
console.error('[Workflow] Error executing step:', err);
|
||||
error.value = 'Failed to execute step';
|
||||
notifyParentOfError(error.value);
|
||||
} finally {
|
||||
stepLoading.value = false;
|
||||
}
|
||||
@ -893,6 +916,139 @@ const goHome = () => {
|
||||
router.push('/');
|
||||
};
|
||||
|
||||
// Update URL when process completes
|
||||
const updateUrlOnComplete = () => {
|
||||
if (isIframeMode.value) {
|
||||
const currentUrl = new URL(window.location.href);
|
||||
currentUrl.searchParams.set('complete', 'true');
|
||||
// Use replaceState to avoid adding to browser history
|
||||
window.history.replaceState({}, '', currentUrl.toString());
|
||||
console.log('[Workflow] URL updated with complete=true');
|
||||
|
||||
// Also notify parent iframe if we're in an iframe
|
||||
try {
|
||||
if (window.parent !== window) {
|
||||
window.parent.postMessage({
|
||||
type: 'workflow-complete',
|
||||
processId: processId.value,
|
||||
processName: process.value?.processName,
|
||||
hideCompletionMessage: hideCompletionMessage.value,
|
||||
timestamp: new Date().toISOString()
|
||||
}, '*');
|
||||
console.log('[Workflow] Message sent to parent iframe');
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('[Workflow] Could not send message to parent:', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Notify parent iframe of errors
|
||||
const notifyParentOfError = (errorMessage) => {
|
||||
if (isIframeMode.value) {
|
||||
try {
|
||||
if (window.parent !== window) {
|
||||
window.parent.postMessage({
|
||||
type: 'workflow-error',
|
||||
processId: processId.value,
|
||||
processName: process.value?.processName,
|
||||
error: errorMessage,
|
||||
timestamp: new Date().toISOString()
|
||||
}, '*');
|
||||
console.log('[Workflow] Error message sent to parent iframe');
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('[Workflow] Could not send error message to parent:', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* PARENT IFRAME INTEGRATION GUIDE:
|
||||
*
|
||||
* The workflow will send messages to the parent iframe when in iframe mode (?debug=false):
|
||||
*
|
||||
* 1. Process Completion:
|
||||
* - URL will be updated with ?complete=true
|
||||
* - Message sent: { type: 'workflow-complete', processId, processName, hideCompletionMessage, timestamp }
|
||||
*
|
||||
* 2. Process Error:
|
||||
* - Message sent: { type: 'workflow-error', processId, processName, error, timestamp }
|
||||
*
|
||||
* URL Parameters:
|
||||
* - ?debug=false: Enable iframe mode (hide UI chrome)
|
||||
* - ?hideComplete=true: Hide completion message (for seamless multi-process workflows)
|
||||
*
|
||||
* Example parent iframe listener:
|
||||
*
|
||||
* window.addEventListener('message', (event) => {
|
||||
* if (event.data.type === 'workflow-complete') {
|
||||
* console.log('Workflow completed:', event.data);
|
||||
* // Handle completion - redirect to next process if needed
|
||||
* if (event.data.hideCompletionMessage) {
|
||||
* // User won't see completion message, redirect immediately
|
||||
* redirectToNextProcess();
|
||||
* }
|
||||
* } else if (event.data.type === 'workflow-error') {
|
||||
* console.log('Workflow error:', event.data);
|
||||
* // Handle error
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* To check URL for completion:
|
||||
* const urlParams = new URLSearchParams(window.location.search);
|
||||
* if (urlParams.get('complete') === 'true') {
|
||||
* // Process is complete
|
||||
* }
|
||||
*
|
||||
* MULTI-PROCESS WORKFLOW EXAMPLE:
|
||||
*
|
||||
* // Parent system with multiple processes
|
||||
* const processes = [
|
||||
* { id: 'process1', name: 'Personal Information' },
|
||||
* { id: 'process2', name: 'Employment Details' },
|
||||
* { id: 'process3', name: 'Document Upload' },
|
||||
* { id: 'process4', name: 'Final Review' }
|
||||
* ];
|
||||
*
|
||||
* let currentProcessIndex = 0;
|
||||
*
|
||||
* function loadProcess(processIndex) {
|
||||
* if (processIndex >= processes.length) {
|
||||
* // All processes complete
|
||||
* showFinalSuccess();
|
||||
* return;
|
||||
* }
|
||||
*
|
||||
* const process = processes[processIndex];
|
||||
* const iframe = document.getElementById('workflow-iframe');
|
||||
*
|
||||
* // Load process with hidden completion message
|
||||
* iframe.src = `/workflow/${process.id}?debug=false&hideComplete=true`;
|
||||
* currentProcessIndex = processIndex;
|
||||
* }
|
||||
*
|
||||
* // Listen for completion and auto-advance
|
||||
* window.addEventListener('message', (event) => {
|
||||
* if (event.data.type === 'workflow-complete') {
|
||||
* console.log(`Process ${event.data.processName} completed`);
|
||||
*
|
||||
* if (event.data.hideCompletionMessage) {
|
||||
* // Seamlessly move to next process
|
||||
* setTimeout(() => {
|
||||
* loadProcess(currentProcessIndex + 1);
|
||||
* }, 100); // Small delay for smooth transition
|
||||
* }
|
||||
* } else if (event.data.type === 'workflow-error') {
|
||||
* console.error('Process failed:', event.data.error);
|
||||
* showErrorMessage(event.data.error);
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* // Start the first process
|
||||
* loadProcess(0);
|
||||
*/
|
||||
|
||||
// Load process on mount
|
||||
onMounted(() => {
|
||||
loadProcess();
|
||||
@ -1219,9 +1375,9 @@ function getConditionGroupResult(conditionGroup, variables) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="min-h-screen bg-gray-50">
|
||||
<!-- Header -->
|
||||
<header class="bg-white border-b border-gray-200 px-6 py-4 shadow-sm">
|
||||
<div :class="isIframeMode ? 'min-h-screen bg-white' : 'min-h-screen bg-gray-50'">
|
||||
<!-- Header - Hidden in iframe mode -->
|
||||
<header v-if="!isIframeMode" class="bg-white border-b border-gray-200 px-6 py-4 shadow-sm">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-4">
|
||||
<Icon
|
||||
@ -1258,7 +1414,7 @@ function getConditionGroupResult(conditionGroup, variables) {
|
||||
</header>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="container mx-auto px-6 py-8 max-w-[1200px]">
|
||||
<div :class="isIframeMode ? 'p-4' : 'container mx-auto px-6 py-8 max-w-[1200px]'">
|
||||
<!-- Loading State -->
|
||||
<div v-if="loading" class="flex justify-center items-center py-12">
|
||||
<div class="text-center">
|
||||
@ -1268,24 +1424,24 @@ function getConditionGroupResult(conditionGroup, variables) {
|
||||
</div>
|
||||
|
||||
<!-- Error State -->
|
||||
<div v-else-if="error" class="bg-white rounded-xl shadow-sm border border-gray-200 p-12 text-center">
|
||||
<div v-else-if="error" :class="isIframeMode ? 'p-6 text-center' : 'bg-white rounded-xl shadow-sm border border-gray-200 p-12 text-center'">
|
||||
<Icon name="material-symbols:error-outline" class="w-16 h-16 text-red-400 mx-auto mb-4" />
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-2">Error</h3>
|
||||
<p class="text-gray-600 mb-6">{{ error }}</p>
|
||||
<RsButton @click="goHome" variant="primary">
|
||||
<RsButton v-if="!isIframeMode" @click="goHome" variant="primary">
|
||||
<Icon name="material-symbols:home" class="mr-2" />
|
||||
Go Home
|
||||
</RsButton>
|
||||
</div>
|
||||
|
||||
<!-- Process Complete -->
|
||||
<div v-else-if="isProcessComplete" class="bg-white rounded-xl shadow-sm border border-gray-200 p-12 text-center">
|
||||
<!-- Process Complete - Hidden when hideComplete=true -->
|
||||
<div v-else-if="isProcessComplete && !hideCompletionMessage" :class="isIframeMode ? 'p-6 text-center' : 'bg-white rounded-xl shadow-sm border border-gray-200 p-12 text-center'">
|
||||
<Icon name="material-symbols:check-circle" class="w-16 h-16 text-green-500 mx-auto mb-4" />
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-2">Process Complete!</h2>
|
||||
<h2 :class="isIframeMode ? 'text-xl font-bold text-gray-900 mb-2' : 'text-2xl font-bold text-gray-900 mb-2'">Process Complete!</h2>
|
||||
<p class="text-gray-600 mb-6">
|
||||
The workflow "{{ process.processName }}" has been completed successfully.
|
||||
</p>
|
||||
<div class="flex justify-center gap-3">
|
||||
<div v-if="!isIframeMode" class="flex justify-center gap-3">
|
||||
<RsButton @click="loadProcess" variant="secondary">
|
||||
<Icon name="material-symbols:refresh" class="mr-2" />
|
||||
Run Again
|
||||
@ -1297,10 +1453,18 @@ function getConditionGroupResult(conditionGroup, variables) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Process Complete but Hidden - Show minimal loading state -->
|
||||
<div v-else-if="isProcessComplete && hideCompletionMessage" class="flex justify-center items-center py-8">
|
||||
<div class="text-center">
|
||||
<Icon name="material-symbols:progress-activity" class="w-6 h-6 animate-spin text-blue-500 mx-auto mb-2" />
|
||||
<p class="text-gray-500 text-sm">Processing...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Current Step -->
|
||||
<div v-else-if="currentNode" class="space-y-6">
|
||||
<!-- Step Info -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
|
||||
<div v-else-if="currentNode" :class="isIframeMode ? 'space-y-2' : 'space-y-6'">
|
||||
<!-- Step Info - Hidden in iframe mode -->
|
||||
<div v-if="!isIframeMode" class="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
|
||||
<div class="flex items-center gap-4 mb-4">
|
||||
<div :class="[
|
||||
'p-3 rounded-lg',
|
||||
@ -1323,8 +1487,8 @@ function getConditionGroupResult(conditionGroup, variables) {
|
||||
</div>
|
||||
|
||||
<!-- Form Step -->
|
||||
<div v-if="currentNode.type === 'form'" class="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
|
||||
<div class="mb-4">
|
||||
<div v-if="currentNode.type === 'form'" :class="isIframeMode ? 'p-2' : 'bg-white rounded-xl shadow-sm border border-gray-200 p-6'">
|
||||
<div v-if="!isIframeMode" class="mb-4">
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-2">
|
||||
{{ currentNode.data?.formName || currentNode.data?.label || 'Please fill out the form' }}
|
||||
</h3>
|
||||
@ -1382,21 +1546,23 @@ function getConditionGroupResult(conditionGroup, variables) {
|
||||
:field-states="fieldStates"
|
||||
/>
|
||||
</template>
|
||||
<div class="flex justify-end pt-6 border-t border-gray-200">
|
||||
<!-- Place submit button in a full-width row at the end of the grid -->
|
||||
<div class="col-span-12 flex justify-start pt-6 border-t border-gray-200">
|
||||
<RsButton
|
||||
@click="validateAndSubmit"
|
||||
:disabled="stepLoading"
|
||||
variant="primary"
|
||||
class="col-span-12"
|
||||
>
|
||||
<Icon v-if="stepLoading" name="material-symbols:progress-activity" class="w-4 h-4 animate-spin mr-2" />
|
||||
{{ stepLoading ? 'Processing...' : 'Submit & Continue' }}
|
||||
{{ stepLoading ? 'Processing...' : 'Submit' }}
|
||||
</RsButton>
|
||||
</div>
|
||||
</div>
|
||||
</FormKit>
|
||||
|
||||
<!-- Script Info (for debugging) -->
|
||||
<div v-if="combinedScript" class="mt-4 p-3 bg-gray-50 rounded-lg border">
|
||||
<!-- Script Info (for debugging) - Hidden in iframe mode -->
|
||||
<div v-if="combinedScript && !isIframeMode" class="mt-4 p-3 bg-gray-50 rounded-lg border">
|
||||
<details>
|
||||
<summary class="text-sm font-medium text-gray-700 cursor-pointer">Form Logic Available (Development)</summary>
|
||||
<div class="mt-2 space-y-2">
|
||||
@ -1425,10 +1591,11 @@ function getConditionGroupResult(conditionGroup, variables) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- API/Script/Notification Step -->
|
||||
<div v-else-if="['api', 'script', 'notification'].includes(currentNode.type)" class="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
|
||||
<!-- Processing State -->
|
||||
<div v-if="stepLoading" class="text-center py-8">
|
||||
<!-- API/Script/Notification Step - Hidden in iframe mode unless multiple paths -->
|
||||
<div v-else-if="['api', 'script', 'notification'].includes(currentNode.type) && (!isIframeMode || !canAutoProgress(currentNode))"
|
||||
:class="isIframeMode ? 'p-2' : 'bg-white rounded-xl shadow-sm border border-gray-200 p-6'">
|
||||
<!-- Processing State - Hidden in iframe mode -->
|
||||
<div v-if="stepLoading && !isIframeMode" class="text-center py-8">
|
||||
<Icon name="material-symbols:progress-activity" class="w-8 h-8 animate-spin text-blue-500 mx-auto mb-4" />
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-2">
|
||||
{{
|
||||
@ -1442,7 +1609,7 @@ function getConditionGroupResult(conditionGroup, variables) {
|
||||
|
||||
<!-- Completed State with Multiple Paths -->
|
||||
<div v-else-if="!canAutoProgress(currentNode)" class="space-y-4">
|
||||
<div class="text-center">
|
||||
<div v-if="!isIframeMode" class="text-center">
|
||||
<Icon name="material-symbols:check-circle" class="w-8 h-8 text-green-500 mx-auto mb-4" />
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-2">
|
||||
{{
|
||||
@ -1455,16 +1622,16 @@ function getConditionGroupResult(conditionGroup, variables) {
|
||||
</div>
|
||||
|
||||
<!-- Multiple Path Options -->
|
||||
<div class="border-t border-gray-200 pt-4">
|
||||
<h4 class="font-medium text-gray-700 mb-3">Choose your next step:</h4>
|
||||
<div :class="isIframeMode ? '' : 'border-t border-gray-200 pt-4'">
|
||||
<h4 v-if="!isIframeMode" class="font-medium text-gray-700 mb-3">Choose your next step:</h4>
|
||||
<div class="grid gap-2">
|
||||
<template v-for="edge in getOutgoingEdges(currentNode.id)" :key="edge.id">
|
||||
<RsButton
|
||||
@click="makeDecision(edge.target)"
|
||||
variant="outline"
|
||||
class="justify-between p-3"
|
||||
:class="isIframeMode ? 'justify-between p-2' : 'justify-between p-3'"
|
||||
>
|
||||
<span>Continue to: {{ getNodeLabel(edge.target) }}</span>
|
||||
<span>{{ isIframeMode ? getNodeLabel(edge.target) : `Continue to: ${getNodeLabel(edge.target)}` }}</span>
|
||||
<Icon name="material-symbols:arrow-right-alt" />
|
||||
</RsButton>
|
||||
</template>
|
||||
@ -1474,8 +1641,8 @@ function getConditionGroupResult(conditionGroup, variables) {
|
||||
</div>
|
||||
|
||||
<!-- HTML Content Step -->
|
||||
<div v-else-if="currentNode.type === 'html'" class="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
|
||||
<div class="mb-4">
|
||||
<div v-else-if="currentNode.type === 'html'" :class="isIframeMode ? 'p-2' : 'bg-white rounded-xl shadow-sm border border-gray-200 p-6'">
|
||||
<div v-if="!isIframeMode" class="mb-4">
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-2">
|
||||
{{ currentNode.data?.title || currentNode.data?.label || 'Content Display' }}
|
||||
</h3>
|
||||
@ -1485,30 +1652,31 @@ function getConditionGroupResult(conditionGroup, variables) {
|
||||
</div>
|
||||
|
||||
<!-- HTML Content -->
|
||||
<div class="html-content-container border border-gray-200 rounded-lg p-4 mb-6">
|
||||
<div :class="isIframeMode ? 'mb-4' : 'html-content-container border border-gray-200 rounded-lg p-4 mb-6'">
|
||||
<div
|
||||
v-if="interpolatedHtmlContent"
|
||||
v-html="interpolatedHtmlContent"
|
||||
class="prose max-w-none"
|
||||
></div>
|
||||
<div v-else class="text-center text-gray-500 py-8">
|
||||
<div v-else-if="!isIframeMode" class="text-center text-gray-500 py-8">
|
||||
<Icon name="material-symbols:code-blocks" class="w-12 h-12 mx-auto mb-2" />
|
||||
<p>No HTML content provided</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Continue Options -->
|
||||
<div class="pt-4 border-t border-gray-200">
|
||||
<div :class="isIframeMode ? 'pt-2' : 'pt-4 border-t border-gray-200'">
|
||||
<!-- Single Path - Simple Continue Button -->
|
||||
<div v-if="canAutoProgress(currentNode)" class="flex justify-end">
|
||||
<div v-if="canAutoProgress(currentNode)" :class="isIframeMode ? 'flex justify-center' : 'flex justify-end'">
|
||||
<RsButton
|
||||
@click="moveToNextStep"
|
||||
:disabled="stepLoading"
|
||||
variant="primary"
|
||||
:class="isIframeMode ? 'px-4 py-2' : ''"
|
||||
>
|
||||
<Icon v-if="stepLoading" name="material-symbols:progress-activity" class="w-4 h-4 animate-spin mr-2" />
|
||||
<template v-if="!stepLoading">
|
||||
Continue to: {{ getNextNodeObject(currentNode)?.data?.label || getNextNodeObject(currentNode)?.label || 'Next Step' }}
|
||||
{{ isIframeMode ? 'Continue' : `Continue to: ${getNextNodeObject(currentNode)?.data?.label || getNextNodeObject(currentNode)?.label || 'Next Step'}` }}
|
||||
</template>
|
||||
<template v-else>
|
||||
Processing...
|
||||
@ -1518,15 +1686,15 @@ function getConditionGroupResult(conditionGroup, variables) {
|
||||
|
||||
<!-- Multiple Paths - Show Options -->
|
||||
<div v-else class="space-y-3">
|
||||
<h4 class="font-medium text-gray-700">Choose your next step:</h4>
|
||||
<h4 v-if="!isIframeMode" class="font-medium text-gray-700">Choose your next step:</h4>
|
||||
<div class="grid gap-2">
|
||||
<template v-for="edge in getOutgoingEdges(currentNode.id)" :key="edge.id">
|
||||
<RsButton
|
||||
@click="makeDecision(edge.target)"
|
||||
variant="outline"
|
||||
class="justify-between p-3"
|
||||
:class="isIframeMode ? 'justify-between p-2' : 'justify-between p-3'"
|
||||
>
|
||||
<span>Continue to: {{ getNodeLabel(edge.target) }}</span>
|
||||
<span>{{ isIframeMode ? getNodeLabel(edge.target) : `Continue to: ${getNodeLabel(edge.target)}` }}</span>
|
||||
<Icon name="material-symbols:arrow-right-alt" />
|
||||
</RsButton>
|
||||
</template>
|
||||
@ -1536,8 +1704,8 @@ function getConditionGroupResult(conditionGroup, variables) {
|
||||
</div>
|
||||
|
||||
<!-- Decision/Gateway Step -->
|
||||
<div v-else-if="['decision', 'gateway'].includes(currentNode.type)" class="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
|
||||
<div class="mb-4">
|
||||
<div v-else-if="['decision', 'gateway'].includes(currentNode.type)" :class="isIframeMode ? 'p-2' : 'bg-white rounded-xl shadow-sm border border-gray-200 p-6'">
|
||||
<div v-if="!isIframeMode" class="mb-4">
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-2">
|
||||
{{ currentNode.data?.title || currentNode.data?.label || 'Make a Decision' }}
|
||||
</h3>
|
||||
@ -1548,17 +1716,17 @@ function getConditionGroupResult(conditionGroup, variables) {
|
||||
|
||||
<!-- Manual Decision Options -->
|
||||
<div v-if="currentNode.data?.executionType === 'manual'" class="space-y-4">
|
||||
<p class="text-gray-700 mb-4">Please choose one of the following options:</p>
|
||||
<p v-if="!isIframeMode" class="text-gray-700 mb-4">Please choose one of the following options:</p>
|
||||
<div class="grid gap-3">
|
||||
<template v-for="edge in workflowData.edges.filter(e => e.source === currentNode.id)" :key="edge.id">
|
||||
<RsButton
|
||||
@click="makeDecision(edge.target)"
|
||||
variant="outline-primary"
|
||||
class="justify-start p-4 h-auto"
|
||||
:class="isIframeMode ? 'justify-start p-2 h-auto' : 'justify-start p-4 h-auto'"
|
||||
>
|
||||
<div class="text-left">
|
||||
<div class="font-medium">{{ edge.label || 'Option' }}</div>
|
||||
<div v-if="edge.data?.description" class="text-sm text-gray-500 mt-1">
|
||||
<div :class="isIframeMode ? 'font-medium text-sm' : 'font-medium'">{{ edge.label || 'Option' }}</div>
|
||||
<div v-if="edge.data?.description && !isIframeMode" class="text-sm text-gray-500 mt-1">
|
||||
{{ edge.data.description }}
|
||||
</div>
|
||||
</div>
|
||||
@ -1568,8 +1736,8 @@ function getConditionGroupResult(conditionGroup, variables) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Automatic Decision Processing -->
|
||||
<div v-else class="text-center py-8">
|
||||
<!-- Automatic Decision Processing - Hidden in iframe mode -->
|
||||
<div v-else-if="!isIframeMode" class="text-center py-8">
|
||||
<Icon name="material-symbols:progress-activity" class="w-8 h-8 animate-spin text-yellow-500 mx-auto mb-4" />
|
||||
<h4 class="text-lg font-medium text-gray-900 mb-2">Evaluating Conditions</h4>
|
||||
<p class="text-gray-600">Please wait while we determine the next step...</p>
|
||||
@ -1623,8 +1791,8 @@ function getConditionGroupResult(conditionGroup, variables) {
|
||||
</RsButton>
|
||||
</div>
|
||||
|
||||
<!-- Variable Mapping Debug (always visible for any node) -->
|
||||
<div v-if="currentNode" class="bg-gray-100 rounded-lg p-4">
|
||||
<!-- Variable Mapping Debug (hidden in iframe mode) -->
|
||||
<div v-if="currentNode && !isIframeMode" class="bg-gray-100 rounded-lg p-4">
|
||||
<details>
|
||||
<summary class="font-medium text-gray-700 cursor-pointer mb-2">Variable Mapping Debug</summary>
|
||||
<div class="space-y-3 text-xs">
|
||||
@ -1662,8 +1830,8 @@ function getConditionGroupResult(conditionGroup, variables) {
|
||||
</details>
|
||||
</div>
|
||||
|
||||
<!-- Process Variables Debug (only in development) -->
|
||||
<div v-if="Object.keys(processVariables || {}).length > 0" class="bg-gray-100 rounded-lg p-4 mt-4">
|
||||
<!-- Process Variables Debug (hidden in iframe mode) -->
|
||||
<div v-if="Object.keys(processVariables || {}).length > 0 && !isIframeMode" class="bg-gray-100 rounded-lg p-4 mt-4">
|
||||
<details>
|
||||
<summary class="font-medium text-gray-700 cursor-pointer mb-2">Process Variables (Debug)</summary>
|
||||
<pre class="text-xs text-gray-600 bg-white p-2 rounded border overflow-auto">{{ JSON.stringify(processVariables, null, 2) }}</pre>
|
||||
|
Loading…
x
Reference in New Issue
Block a user