version 2
This commit is contained in:
parent
852719d2c1
commit
1cd6cbb6dc
49
FOLDER_STRUCTURE.md
Normal file
49
FOLDER_STRUCTURE.md
Normal file
@ -0,0 +1,49 @@
|
||||
# Project Folder Structure
|
||||
|
||||
This document outlines the recommended GitHub-ready layout for your CulturalSync AI project.
|
||||
|
||||
```
|
||||
/
|
||||
├── client/ # Frontend (React + Tailwind)
|
||||
│ ├── public/
|
||||
│ └── src/
|
||||
│ ├── assets/
|
||||
│ ├── components/
|
||||
│ ├── hooks/
|
||||
│ ├── pages/
|
||||
│ ├── stores/
|
||||
│ ├── styles/
|
||||
│ └── utils/
|
||||
│ ├── package.json
|
||||
│ ├── tailwind.config.js
|
||||
│ └── tsconfig.json
|
||||
|
||||
├── server/ # Backend (Node.js + Express + MongoDB)
|
||||
│ ├── models/ # Mongoose schemas
|
||||
│ ├── routes/ # Express routers (users, orgs, workflows, etc.)
|
||||
│ ├── middleware/ # Auth, error handlers, RBAC checks
|
||||
│ ├── utils/ # Helpers (email, logger, config)
|
||||
│ ├── controllers/ # Business logic for each route
|
||||
│ ├── services/ # External integrations (n8n, X API, AI, etc.)
|
||||
│ ├── config/ # Database, environment variables, constants
|
||||
│ ├── index.js # App entry point
|
||||
│ ├── package.json
|
||||
│ └── .env.example
|
||||
|
||||
├── scripts/
|
||||
│ └── seed.js # Seed sample MongoDB data (orgs, users, workflows, logs)
|
||||
|
||||
├── Dockerfile # Multi-stage build for client & server
|
||||
├── docker-compose.yml # (Optional) local dev orchestration
|
||||
├── .gitignore
|
||||
├── README.md # Project overview & setup instructions
|
||||
└── FOLDER_STRUCTURE.md # (This file)
|
||||
```
|
||||
|
||||
Next steps:
|
||||
1. Move your existing frontend code into `client/src/`.
|
||||
2. Move backend code into `server/`, splitting models, routes, middleware, etc.
|
||||
3. Update root-level Dockerfile/docker-compose.yml to reference `client` and `server`.
|
||||
4. Drop `scripts/seed.js` in place for sample data.
|
||||
|
||||
Commit this structure before adding custom modules (n8n embed, AI prompts, compliance checks).
|
||||
File diff suppressed because one or more lines are too long
@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Seeder: Enhance execution metrics and inject default workflow templates per organization
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
up: async (queryInterface, Sequelize) => {
|
||||
// 1. Update all executions with random durationMs and alertCount
|
||||
await queryInterface.sequelize.query(`
|
||||
UPDATE "Executions"
|
||||
SET "durationMs" = FLOOR(RANDOM() * (2000 - 200) + 200),
|
||||
"alertCount" = FLOOR(RANDOM() * 6)
|
||||
`);
|
||||
|
||||
// 2. Insert default workflow templates for each organization if not present
|
||||
const [orgs] = await queryInterface.sequelize.query(
|
||||
'SELECT id FROM "Organizations"'
|
||||
);
|
||||
|
||||
const templates = [
|
||||
{ name: "Lunar New Year Promo", description: "Localized campaign for Singapore market" },
|
||||
{ name: "Diwali WhatsApp Funnel", description: "WhatsApp push for India" },
|
||||
{ name: "Māori Festival Email", description: "NZ market outreach" },
|
||||
{ name: "GDPR Lead Magnet Funnel", description: "EU-compliant workflow" }
|
||||
];
|
||||
|
||||
for (const org of orgs) {
|
||||
for (const template of templates) {
|
||||
const [[existing]] = await queryInterface.sequelize.query(
|
||||
`SELECT id FROM "Workflows" WHERE name = :name AND "organizationId" = :orgId`,
|
||||
{
|
||||
replacements: { name: template.name, orgId: org.id },
|
||||
type: Sequelize.QueryTypes.SELECT
|
||||
}
|
||||
);
|
||||
if (!existing) {
|
||||
await queryInterface.bulkInsert(
|
||||
'Workflows',
|
||||
[{
|
||||
name: template.name,
|
||||
description: template.description,
|
||||
nodes: JSON.stringify([]),
|
||||
organizationId: org.id,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
}],
|
||||
{}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
down: async (queryInterface, Sequelize) => {
|
||||
// Revert random metrics
|
||||
await queryInterface.sequelize.query(`
|
||||
UPDATE "Executions"
|
||||
SET "durationMs" = NULL, "alertCount" = NULL
|
||||
`);
|
||||
|
||||
// Remove injected workflow templates
|
||||
await queryInterface.bulkDelete(
|
||||
'Workflows',
|
||||
{
|
||||
name: [
|
||||
"Lunar New Year Promo",
|
||||
"Diwali WhatsApp Funnel",
|
||||
"Māori Festival Email",
|
||||
"GDPR Lead Magnet Funnel"
|
||||
]
|
||||
},
|
||||
{}
|
||||
);
|
||||
}
|
||||
};
|
||||
@ -1,4 +1,6 @@
|
||||
const express = require('express');
|
||||
const db = require('../db/models');
|
||||
|
||||
|
||||
const WorkflowsService = require('../services/workflows');
|
||||
const WorkflowsDBApi = require('../db/api/workflows');
|
||||
@ -439,6 +441,31 @@ router.get('/autocomplete', async (req, res) => {
|
||||
* 404:
|
||||
* description: Item not found
|
||||
* 500:
|
||||
// Route: Get latest execution status for a workflow
|
||||
router.get(
|
||||
'/:id/execution-status',
|
||||
wrapAsync(async (req, res) => {
|
||||
const workflowId = req.params.id;
|
||||
const execution = await db.executions.findOne({
|
||||
where: { workflowId },
|
||||
order: [['createdAt', 'DESC']],
|
||||
include: [{ model: db.compliance_logs, as: 'compliance_logs_execution' }],
|
||||
});
|
||||
if (!execution) {
|
||||
return res.status(404).json({ error: 'No execution found for workflow.' });
|
||||
}
|
||||
const alerts = execution.compliance_logs_execution
|
||||
? execution.compliance_logs_execution.map((log) => log.message)
|
||||
: [];
|
||||
res.json({
|
||||
status: execution.status,
|
||||
durationMs: execution.durationMs,
|
||||
outputSnippet: execution.output_snippet,
|
||||
alerts,
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
* description: Some server error
|
||||
*/
|
||||
router.get(
|
||||
|
||||
1
frontend/json/runtimeError.json
Normal file
1
frontend/json/runtimeError.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
||||
@ -60,6 +60,15 @@ const menuAside: MenuAsideItem[] = [
|
||||
: icon.mdiTable ?? icon.mdiTable,
|
||||
permissions: 'READ_WORKFLOWS',
|
||||
},
|
||||
{
|
||||
href: '/workflows/builder',
|
||||
label: 'Builder',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
icon: icon['mdiWand2'] ?? icon.mdiTable,
|
||||
permissions: 'READ_WORKFLOWS',
|
||||
},
|
||||
|
||||
{
|
||||
href: '/roles/roles-list',
|
||||
label: 'Roles',
|
||||
|
||||
108
frontend/src/pages/workflows/builder.tsx
Normal file
108
frontend/src/pages/workflows/builder.tsx
Normal file
@ -0,0 +1,108 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import axios from 'axios';
|
||||
import { useRouter } from 'next/router';
|
||||
import Head from 'next/head';
|
||||
import LayoutAuthenticated from '../../layouts/Authenticated';
|
||||
|
||||
const WorkflowsBuilderPage = () => {
|
||||
const router = useRouter();
|
||||
const { workflowId: paramId } = router.query;
|
||||
const [workflows, setWorkflows] = useState([]);
|
||||
const [workflowId, setWorkflowId] = useState(paramId || '');
|
||||
const [executing, setExecuting] = useState(false);
|
||||
const [message, setMessage] = useState('');
|
||||
const [executionStatus, setExecutionStatus] = useState(null);
|
||||
const [polling, setPolling] = useState(false);
|
||||
const pollRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
axios.get('/workflows')
|
||||
.then(res => setWorkflows(res.data.rows || []))
|
||||
.catch(err => console.error('Error fetching workflows:', err));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (paramId) {
|
||||
setWorkflowId(paramId.toString());
|
||||
}
|
||||
}, [paramId]);
|
||||
|
||||
const executeWorkflow = async () => {
|
||||
if (!workflowId) return;
|
||||
setExecuting(true);
|
||||
setMessage('');
|
||||
try {
|
||||
const res = await axios.post(`/workflows/${workflowId}/execute`);
|
||||
setMessage(res.data.success ? 'Execution started.' : 'Execution failed.');
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
setMessage('Error triggering execution');
|
||||
}
|
||||
setExecuting(false);
|
||||
};
|
||||
|
||||
const srcUrl = workflowId
|
||||
? `http://localhost:5678/workflow/${workflowId}`
|
||||
: 'http://localhost:5678/workflow';
|
||||
|
||||
return (
|
||||
<div className="h-screen w-full bg-white p-4 flex flex-col">
|
||||
<Head>
|
||||
<title>Workflow Builder | CulturalSync AI</title>
|
||||
</Head>
|
||||
<h1 className="text-2xl font-semibold mb-4">Workflow Builder</h1>
|
||||
<div className="mb-4 flex items-center space-x-2">
|
||||
<select
|
||||
className="p-2 border rounded"
|
||||
value={workflowId}
|
||||
onChange={e => setWorkflowId(e.target.value)}
|
||||
>
|
||||
<option value="">Select a workflow...</option>
|
||||
{workflows.map(wf => (
|
||||
<option key={wf.id} value={wf.id}>{wf.name}</option>
|
||||
))}
|
||||
</select>
|
||||
<button
|
||||
onClick={executeWorkflow}
|
||||
disabled={!workflowId || executing}
|
||||
className={`px-4 py-2 rounded text-white ${executing ? 'bg-gray-400' : 'bg-indigo-600 hover:bg-indigo-700'}`}
|
||||
>
|
||||
{executing ? 'Executing...' : 'Run Workflow'}
|
||||
|
||||
{executionStatus && (
|
||||
<div className="mt-6 p-4 border rounded-lg bg-gray-50">
|
||||
<p><strong>Status:</strong> {executionStatus.status}</p>
|
||||
<p><strong>Duration:</strong> {executionStatus.durationMs} ms</p>
|
||||
<p><strong>Output:</strong> {executionStatus.outputSnippet?.slice(0, 300)}...</p>
|
||||
{executionStatus.alerts?.length > 0 && (
|
||||
<div className="text-red-600 mt-2">
|
||||
<strong>Compliance Alerts:</strong>
|
||||
<ul>
|
||||
{executionStatus.alerts.map((alert, i) => <li key={i}>⚠️ {alert}</li>)}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
</button>
|
||||
</div>
|
||||
{message && <div className="mb-2 text-sm text-green-600">{message}</div>}
|
||||
<iframe
|
||||
src={srcUrl}
|
||||
title="n8n Embedded Canvas"
|
||||
width="100%"
|
||||
height="calc(100vh - 160px)"
|
||||
frameBorder="0"
|
||||
allowFullScreen
|
||||
className="rounded-xl shadow-md border flex-1"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
WorkflowsBuilderPage.getLayout = function getLayout(page) {
|
||||
return <LayoutAuthenticated>{page}</LayoutAuthenticated>;
|
||||
};
|
||||
|
||||
export default WorkflowsBuilderPage;
|
||||
Loading…
x
Reference in New Issue
Block a user