Compare commits
No commits in common. "ai-dev" and "master" have entirely different histories.
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,8 +1,3 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
*/node_modules/
|
*/node_modules/
|
||||||
*/build/
|
*/build/
|
||||||
|
|
||||||
**/node_modules/
|
|
||||||
**/build/
|
|
||||||
.DS_Store
|
|
||||||
.env
|
|
||||||
@ -1,49 +0,0 @@
|
|||||||
# 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
@ -1,75 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -17,14 +17,7 @@ module.exports = class Helpers {
|
|||||||
return res.status(500).send(error.message);
|
return res.status(500).send(error.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
static sanitizeWorkflow(workflow) {
|
static jwtSign(data) {
|
||||||
const { id, name, description, nodes, createdAt, updatedAt } = workflow;
|
return jwt.sign(data, config.secret_key, { expiresIn: '6h' });
|
||||||
return { id, name, description, nodes, createdAt, updatedAt };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static sanitizeOrganization(org) {
|
|
||||||
const { id, name, region, industry, logoUrl, createdAt } = org;
|
|
||||||
return { id, name, region, industry, logoUrl, createdAt };
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -128,14 +128,7 @@ app.use(
|
|||||||
|
|
||||||
app.use(
|
app.use(
|
||||||
'/api/workflows',
|
'/api/workflows',
|
||||||
(req, res, next) => {
|
passport.authenticate('jwt', { session: false }),
|
||||||
const segments = req.path.split('/').filter(Boolean);
|
|
||||||
const publicGetById = req.method === 'GET' && segments.length === 1;
|
|
||||||
if (publicGetById) {
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
return passport.authenticate('jwt', { session: false })(req, res, next);
|
|
||||||
},
|
|
||||||
workflowsRoutes,
|
workflowsRoutes,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -153,12 +146,7 @@ app.use(
|
|||||||
|
|
||||||
app.use(
|
app.use(
|
||||||
'/api/organizations',
|
'/api/organizations',
|
||||||
(req, res, next) => {
|
passport.authenticate('jwt', { session: false }),
|
||||||
const segments = req.path.split('/').filter(Boolean);
|
|
||||||
const publicGetById = req.method === 'GET' && segments.length === 1;
|
|
||||||
if (publicGetById) return next();
|
|
||||||
return passport.authenticate('jwt', { session: false })(req, res, next);
|
|
||||||
},
|
|
||||||
organizationsRoutes,
|
organizationsRoutes,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,6 @@ const express = require('express');
|
|||||||
const OrganizationsService = require('../services/organizations');
|
const OrganizationsService = require('../services/organizations');
|
||||||
const OrganizationsDBApi = require('../db/api/organizations');
|
const OrganizationsDBApi = require('../db/api/organizations');
|
||||||
const wrapAsync = require('../helpers').wrapAsync;
|
const wrapAsync = require('../helpers').wrapAsync;
|
||||||
const Helpers = require('../helpers');
|
|
||||||
|
|
||||||
const config = require('../config');
|
const config = require('../config');
|
||||||
|
|
||||||
|
|||||||
@ -1,459 +0,0 @@
|
|||||||
const express = require('express');
|
|
||||||
|
|
||||||
const OrganizationsService = require('../services/organizations');
|
|
||||||
const OrganizationsDBApi = require('../db/api/organizations');
|
|
||||||
const wrapAsync = require('../helpers').wrapAsync;
|
|
||||||
const Helpers = require('../helpers');
|
|
||||||
|
|
||||||
const config = require('../config');
|
|
||||||
|
|
||||||
const router = express.Router();
|
|
||||||
|
|
||||||
const { parse } = require('json2csv');
|
|
||||||
|
|
||||||
const { checkCrudPermissions } = require('../middlewares/check-permissions');
|
|
||||||
|
|
||||||
router.use(checkCrudPermissions('organizations'));
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @swagger
|
|
||||||
* components:
|
|
||||||
* schemas:
|
|
||||||
* Organizations:
|
|
||||||
* type: object
|
|
||||||
* properties:
|
|
||||||
|
|
||||||
* name:
|
|
||||||
* type: string
|
|
||||||
* default: name
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @swagger
|
|
||||||
* tags:
|
|
||||||
* name: Organizations
|
|
||||||
* description: The Organizations managing API
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @swagger
|
|
||||||
* /api/organizations:
|
|
||||||
* post:
|
|
||||||
* security:
|
|
||||||
* - bearerAuth: []
|
|
||||||
* tags: [Organizations]
|
|
||||||
* summary: Add new item
|
|
||||||
* description: Add new item
|
|
||||||
* requestBody:
|
|
||||||
* required: true
|
|
||||||
* content:
|
|
||||||
* application/json:
|
|
||||||
* schema:
|
|
||||||
* properties:
|
|
||||||
* data:
|
|
||||||
* description: Data of the updated item
|
|
||||||
* type: object
|
|
||||||
* $ref: "#/components/schemas/Organizations"
|
|
||||||
* responses:
|
|
||||||
* 200:
|
|
||||||
* description: The item was successfully added
|
|
||||||
* content:
|
|
||||||
* application/json:
|
|
||||||
* schema:
|
|
||||||
* $ref: "#/components/schemas/Organizations"
|
|
||||||
* 401:
|
|
||||||
* $ref: "#/components/responses/UnauthorizedError"
|
|
||||||
* 405:
|
|
||||||
* description: Invalid input data
|
|
||||||
* 500:
|
|
||||||
* description: Some server error
|
|
||||||
*/
|
|
||||||
router.post(
|
|
||||||
'/',
|
|
||||||
wrapAsync(async (req, res) => {
|
|
||||||
const referer =
|
|
||||||
req.headers.referer ||
|
|
||||||
`${req.protocol}://${req.hostname}${req.originalUrl}`;
|
|
||||||
const link = new URL(referer);
|
|
||||||
await OrganizationsService.create(
|
|
||||||
req.body.data,
|
|
||||||
req.currentUser,
|
|
||||||
true,
|
|
||||||
link.host,
|
|
||||||
);
|
|
||||||
const payload = true;
|
|
||||||
res.status(200).send(payload);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @swagger
|
|
||||||
* /api/budgets/bulk-import:
|
|
||||||
* post:
|
|
||||||
* security:
|
|
||||||
* - bearerAuth: []
|
|
||||||
* tags: [Organizations]
|
|
||||||
* summary: Bulk import items
|
|
||||||
* description: Bulk import items
|
|
||||||
* requestBody:
|
|
||||||
* required: true
|
|
||||||
* content:
|
|
||||||
* application/json:
|
|
||||||
* schema:
|
|
||||||
* properties:
|
|
||||||
* data:
|
|
||||||
* description: Data of the updated items
|
|
||||||
* type: array
|
|
||||||
* items:
|
|
||||||
* $ref: "#/components/schemas/Organizations"
|
|
||||||
* responses:
|
|
||||||
* 200:
|
|
||||||
* description: The items were successfully imported
|
|
||||||
* content:
|
|
||||||
* application/json:
|
|
||||||
* schema:
|
|
||||||
* $ref: "#/components/schemas/Organizations"
|
|
||||||
* 401:
|
|
||||||
* $ref: "#/components/responses/UnauthorizedError"
|
|
||||||
* 405:
|
|
||||||
* description: Invalid input data
|
|
||||||
* 500:
|
|
||||||
* description: Some server error
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
router.post(
|
|
||||||
'/bulk-import',
|
|
||||||
wrapAsync(async (req, res) => {
|
|
||||||
const referer =
|
|
||||||
req.headers.referer ||
|
|
||||||
`${req.protocol}://${req.hostname}${req.originalUrl}`;
|
|
||||||
const link = new URL(referer);
|
|
||||||
await OrganizationsService.bulkImport(req, res, true, link.host);
|
|
||||||
const payload = true;
|
|
||||||
res.status(200).send(payload);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @swagger
|
|
||||||
* /api/organizations/{id}:
|
|
||||||
* put:
|
|
||||||
* security:
|
|
||||||
* - bearerAuth: []
|
|
||||||
* tags: [Organizations]
|
|
||||||
* summary: Update the data of the selected item
|
|
||||||
* description: Update the data of the selected item
|
|
||||||
* parameters:
|
|
||||||
* - in: path
|
|
||||||
* name: id
|
|
||||||
* description: Item ID to update
|
|
||||||
* required: true
|
|
||||||
* schema:
|
|
||||||
* type: string
|
|
||||||
* requestBody:
|
|
||||||
* description: Set new item data
|
|
||||||
* required: true
|
|
||||||
* content:
|
|
||||||
* application/json:
|
|
||||||
* schema:
|
|
||||||
* properties:
|
|
||||||
* id:
|
|
||||||
* description: ID of the updated item
|
|
||||||
* type: string
|
|
||||||
* data:
|
|
||||||
* description: Data of the updated item
|
|
||||||
* type: object
|
|
||||||
* $ref: "#/components/schemas/Organizations"
|
|
||||||
* required:
|
|
||||||
* - id
|
|
||||||
* responses:
|
|
||||||
* 200:
|
|
||||||
* description: The item data was successfully updated
|
|
||||||
* content:
|
|
||||||
* application/json:
|
|
||||||
* schema:
|
|
||||||
* $ref: "#/components/schemas/Organizations"
|
|
||||||
* 400:
|
|
||||||
* description: Invalid ID supplied
|
|
||||||
* 401:
|
|
||||||
* $ref: "#/components/responses/UnauthorizedError"
|
|
||||||
* 404:
|
|
||||||
* description: Item not found
|
|
||||||
* 500:
|
|
||||||
* description: Some server error
|
|
||||||
*/
|
|
||||||
router.put(
|
|
||||||
'/:id',
|
|
||||||
wrapAsync(async (req, res) => {
|
|
||||||
await OrganizationsService.update(
|
|
||||||
req.body.data,
|
|
||||||
req.body.id,
|
|
||||||
req.currentUser,
|
|
||||||
);
|
|
||||||
const payload = true;
|
|
||||||
res.status(200).send(payload);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @swagger
|
|
||||||
* /api/organizations/{id}:
|
|
||||||
* delete:
|
|
||||||
* security:
|
|
||||||
* - bearerAuth: []
|
|
||||||
* tags: [Organizations]
|
|
||||||
* summary: Delete the selected item
|
|
||||||
* description: Delete the selected item
|
|
||||||
* parameters:
|
|
||||||
* - in: path
|
|
||||||
* name: id
|
|
||||||
* description: Item ID to delete
|
|
||||||
* required: true
|
|
||||||
* schema:
|
|
||||||
* type: string
|
|
||||||
* responses:
|
|
||||||
* 200:
|
|
||||||
* description: The item was successfully deleted
|
|
||||||
* content:
|
|
||||||
* application/json:
|
|
||||||
* schema:
|
|
||||||
* $ref: "#/components/schemas/Organizations"
|
|
||||||
* 400:
|
|
||||||
* description: Invalid ID supplied
|
|
||||||
* 401:
|
|
||||||
* $ref: "#/components/responses/UnauthorizedError"
|
|
||||||
* 404:
|
|
||||||
* description: Item not found
|
|
||||||
* 500:
|
|
||||||
* description: Some server error
|
|
||||||
*/
|
|
||||||
router.delete(
|
|
||||||
'/:id',
|
|
||||||
wrapAsync(async (req, res) => {
|
|
||||||
await OrganizationsService.remove(req.params.id, req.currentUser);
|
|
||||||
const payload = true;
|
|
||||||
res.status(200).send(payload);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @swagger
|
|
||||||
* /api/organizations/deleteByIds:
|
|
||||||
* post:
|
|
||||||
* security:
|
|
||||||
* - bearerAuth: []
|
|
||||||
* tags: [Organizations]
|
|
||||||
* summary: Delete the selected item list
|
|
||||||
* description: Delete the selected item list
|
|
||||||
* requestBody:
|
|
||||||
* required: true
|
|
||||||
* content:
|
|
||||||
* application/json:
|
|
||||||
* schema:
|
|
||||||
* properties:
|
|
||||||
* ids:
|
|
||||||
* description: IDs of the updated items
|
|
||||||
* type: array
|
|
||||||
* responses:
|
|
||||||
* 200:
|
|
||||||
* description: The items was successfully deleted
|
|
||||||
* content:
|
|
||||||
* application/json:
|
|
||||||
* schema:
|
|
||||||
* $ref: "#/components/schemas/Organizations"
|
|
||||||
* 401:
|
|
||||||
* $ref: "#/components/responses/UnauthorizedError"
|
|
||||||
* 404:
|
|
||||||
* description: Items not found
|
|
||||||
* 500:
|
|
||||||
* description: Some server error
|
|
||||||
*/
|
|
||||||
router.post(
|
|
||||||
'/deleteByIds',
|
|
||||||
wrapAsync(async (req, res) => {
|
|
||||||
await OrganizationsService.deleteByIds(req.body.data, req.currentUser);
|
|
||||||
const payload = true;
|
|
||||||
res.status(200).send(payload);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @swagger
|
|
||||||
* /api/organizations:
|
|
||||||
* get:
|
|
||||||
* security:
|
|
||||||
* - bearerAuth: []
|
|
||||||
* tags: [Organizations]
|
|
||||||
* summary: Get all organizations
|
|
||||||
* description: Get all organizations
|
|
||||||
* responses:
|
|
||||||
* 200:
|
|
||||||
* description: Organizations list successfully received
|
|
||||||
* content:
|
|
||||||
* application/json:
|
|
||||||
* schema:
|
|
||||||
* type: array
|
|
||||||
* items:
|
|
||||||
* $ref: "#/components/schemas/Organizations"
|
|
||||||
* 401:
|
|
||||||
* $ref: "#/components/responses/UnauthorizedError"
|
|
||||||
* 404:
|
|
||||||
* description: Data not found
|
|
||||||
* 500:
|
|
||||||
* description: Some server error
|
|
||||||
*/
|
|
||||||
router.get(
|
|
||||||
'/',
|
|
||||||
wrapAsync(async (req, res) => {
|
|
||||||
const filetype = req.query.filetype;
|
|
||||||
|
|
||||||
const globalAccess = req.currentUser.app_role.globalAccess;
|
|
||||||
|
|
||||||
const currentUser = req.currentUser;
|
|
||||||
const payload = await OrganizationsDBApi.findAll(req.query, globalAccess, {
|
|
||||||
currentUser,
|
|
||||||
});
|
|
||||||
if (filetype && filetype === 'csv') {
|
|
||||||
const fields = ['id', 'name'];
|
|
||||||
const opts = { fields };
|
|
||||||
try {
|
|
||||||
const csv = parse(payload.rows, opts);
|
|
||||||
res.status(200).attachment(csv);
|
|
||||||
res.send(csv);
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
res.status(200).send(payload);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @swagger
|
|
||||||
* /api/organizations/count:
|
|
||||||
* get:
|
|
||||||
* security:
|
|
||||||
* - bearerAuth: []
|
|
||||||
* tags: [Organizations]
|
|
||||||
* summary: Count all organizations
|
|
||||||
* description: Count all organizations
|
|
||||||
* responses:
|
|
||||||
* 200:
|
|
||||||
* description: Organizations count successfully received
|
|
||||||
* content:
|
|
||||||
* application/json:
|
|
||||||
* schema:
|
|
||||||
* type: array
|
|
||||||
* items:
|
|
||||||
* $ref: "#/components/schemas/Organizations"
|
|
||||||
* 401:
|
|
||||||
* $ref: "#/components/responses/UnauthorizedError"
|
|
||||||
* 404:
|
|
||||||
* description: Data not found
|
|
||||||
* 500:
|
|
||||||
* description: Some server error
|
|
||||||
*/
|
|
||||||
router.get(
|
|
||||||
'/count',
|
|
||||||
wrapAsync(async (req, res) => {
|
|
||||||
const globalAccess = req.currentUser.app_role.globalAccess;
|
|
||||||
|
|
||||||
const currentUser = req.currentUser;
|
|
||||||
const payload = await OrganizationsDBApi.findAll(req.query, globalAccess, {
|
|
||||||
countOnly: true,
|
|
||||||
currentUser,
|
|
||||||
});
|
|
||||||
|
|
||||||
res.status(200).send(payload);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @swagger
|
|
||||||
* /api/organizations/autocomplete:
|
|
||||||
* get:
|
|
||||||
* security:
|
|
||||||
* - bearerAuth: []
|
|
||||||
* tags: [Organizations]
|
|
||||||
* summary: Find all organizations that match search criteria
|
|
||||||
* description: Find all organizations that match search criteria
|
|
||||||
* responses:
|
|
||||||
* 200:
|
|
||||||
* description: Organizations list successfully received
|
|
||||||
* content:
|
|
||||||
* application/json:
|
|
||||||
* schema:
|
|
||||||
* type: array
|
|
||||||
* items:
|
|
||||||
* $ref: "#/components/schemas/Organizations"
|
|
||||||
* 401:
|
|
||||||
* $ref: "#/components/responses/UnauthorizedError"
|
|
||||||
* 404:
|
|
||||||
* description: Data not found
|
|
||||||
* 500:
|
|
||||||
* description: Some server error
|
|
||||||
*/
|
|
||||||
router.get('/autocomplete', async (req, res) => {
|
|
||||||
const globalAccess = req.currentUser.app_role.globalAccess;
|
|
||||||
|
|
||||||
const organizationId = req.currentUser.organization?.id;
|
|
||||||
|
|
||||||
const payload = await OrganizationsDBApi.findAllAutocomplete(
|
|
||||||
req.query.query,
|
|
||||||
req.query.limit,
|
|
||||||
req.query.offset,
|
|
||||||
globalAccess,
|
|
||||||
organizationId,
|
|
||||||
);
|
|
||||||
|
|
||||||
res.status(200).send(payload);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @swagger
|
|
||||||
* /api/organizations/{id}:
|
|
||||||
* get:
|
|
||||||
* security:
|
|
||||||
* - bearerAuth: []
|
|
||||||
* tags: [Organizations]
|
|
||||||
* summary: Get selected item
|
|
||||||
* description: Get selected item
|
|
||||||
* parameters:
|
|
||||||
* - in: path
|
|
||||||
* name: id
|
|
||||||
* description: ID of item to get
|
|
||||||
* required: true
|
|
||||||
* schema:
|
|
||||||
* type: string
|
|
||||||
* responses:
|
|
||||||
* 200:
|
|
||||||
* description: Selected item successfully received
|
|
||||||
* content:
|
|
||||||
* application/json:
|
|
||||||
* schema:
|
|
||||||
* $ref: "#/components/schemas/Organizations"
|
|
||||||
* 400:
|
|
||||||
* description: Invalid ID supplied
|
|
||||||
* 401:
|
|
||||||
* $ref: "#/components/responses/UnauthorizedError"
|
|
||||||
* 404:
|
|
||||||
* description: Item not found
|
|
||||||
* 500:
|
|
||||||
* description: Some server error
|
|
||||||
router.get(
|
|
||||||
'/:id',
|
|
||||||
wrapAsync(async (req, res) => {
|
|
||||||
const payload = await OrganizationsDBApi.findBy({ id: req.params.id });
|
|
||||||
if (!payload) {
|
|
||||||
return res.status(404).json({ error: 'Not found' });
|
|
||||||
}
|
|
||||||
res.json(Helpers.sanitizeOrganization(payload));
|
|
||||||
})
|
|
||||||
);
|
|
||||||
);
|
|
||||||
|
|
||||||
router.use('/', require('../helpers').commonErrorHandler);
|
|
||||||
|
|
||||||
module.exports = router;
|
|
||||||
@ -1,11 +1,8 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const db = require('../db/models');
|
|
||||||
|
|
||||||
|
|
||||||
const WorkflowsService = require('../services/workflows');
|
const WorkflowsService = require('../services/workflows');
|
||||||
const WorkflowsDBApi = require('../db/api/workflows');
|
const WorkflowsDBApi = require('../db/api/workflows');
|
||||||
const wrapAsync = require('../helpers').wrapAsync;
|
const wrapAsync = require('../helpers').wrapAsync;
|
||||||
const Helpers = require('../helpers');
|
|
||||||
|
|
||||||
const config = require('../config');
|
const config = require('../config');
|
||||||
|
|
||||||
@ -442,31 +439,6 @@ router.get('/autocomplete', async (req, res) => {
|
|||||||
* 404:
|
* 404:
|
||||||
* description: Item not found
|
* description: Item not found
|
||||||
* 500:
|
* 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
|
* description: Some server error
|
||||||
*/
|
*/
|
||||||
router.get(
|
router.get(
|
||||||
|
|||||||
@ -1,486 +0,0 @@
|
|||||||
const express = require('express');
|
|
||||||
const db = require('../db/models');
|
|
||||||
|
|
||||||
|
|
||||||
const WorkflowsService = require('../services/workflows');
|
|
||||||
const WorkflowsDBApi = require('../db/api/workflows');
|
|
||||||
const wrapAsync = require('../helpers').wrapAsync;
|
|
||||||
const Helpers = require('../helpers');
|
|
||||||
|
|
||||||
const config = require('../config');
|
|
||||||
|
|
||||||
const router = express.Router();
|
|
||||||
|
|
||||||
const { parse } = require('json2csv');
|
|
||||||
|
|
||||||
const { checkCrudPermissions } = require('../middlewares/check-permissions');
|
|
||||||
|
|
||||||
router.use(checkCrudPermissions('workflows'));
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @swagger
|
|
||||||
* components:
|
|
||||||
* schemas:
|
|
||||||
* Workflows:
|
|
||||||
* type: object
|
|
||||||
* properties:
|
|
||||||
|
|
||||||
* name:
|
|
||||||
* type: string
|
|
||||||
* default: name
|
|
||||||
* description:
|
|
||||||
* type: string
|
|
||||||
* default: description
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @swagger
|
|
||||||
* tags:
|
|
||||||
* name: Workflows
|
|
||||||
* description: The Workflows managing API
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @swagger
|
|
||||||
* /api/workflows:
|
|
||||||
* post:
|
|
||||||
* security:
|
|
||||||
* - bearerAuth: []
|
|
||||||
* tags: [Workflows]
|
|
||||||
* summary: Add new item
|
|
||||||
* description: Add new item
|
|
||||||
* requestBody:
|
|
||||||
* required: true
|
|
||||||
* content:
|
|
||||||
* application/json:
|
|
||||||
* schema:
|
|
||||||
* properties:
|
|
||||||
* data:
|
|
||||||
* description: Data of the updated item
|
|
||||||
* type: object
|
|
||||||
* $ref: "#/components/schemas/Workflows"
|
|
||||||
* responses:
|
|
||||||
* 200:
|
|
||||||
* description: The item was successfully added
|
|
||||||
* content:
|
|
||||||
* application/json:
|
|
||||||
* schema:
|
|
||||||
* $ref: "#/components/schemas/Workflows"
|
|
||||||
* 401:
|
|
||||||
* $ref: "#/components/responses/UnauthorizedError"
|
|
||||||
* 405:
|
|
||||||
* description: Invalid input data
|
|
||||||
* 500:
|
|
||||||
* description: Some server error
|
|
||||||
*/
|
|
||||||
router.post(
|
|
||||||
'/',
|
|
||||||
wrapAsync(async (req, res) => {
|
|
||||||
const referer =
|
|
||||||
req.headers.referer ||
|
|
||||||
`${req.protocol}://${req.hostname}${req.originalUrl}`;
|
|
||||||
const link = new URL(referer);
|
|
||||||
await WorkflowsService.create(
|
|
||||||
req.body.data,
|
|
||||||
req.currentUser,
|
|
||||||
true,
|
|
||||||
link.host,
|
|
||||||
);
|
|
||||||
const payload = true;
|
|
||||||
res.status(200).send(payload);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @swagger
|
|
||||||
* /api/budgets/bulk-import:
|
|
||||||
* post:
|
|
||||||
* security:
|
|
||||||
* - bearerAuth: []
|
|
||||||
* tags: [Workflows]
|
|
||||||
* summary: Bulk import items
|
|
||||||
* description: Bulk import items
|
|
||||||
* requestBody:
|
|
||||||
* required: true
|
|
||||||
* content:
|
|
||||||
* application/json:
|
|
||||||
* schema:
|
|
||||||
* properties:
|
|
||||||
* data:
|
|
||||||
* description: Data of the updated items
|
|
||||||
* type: array
|
|
||||||
* items:
|
|
||||||
* $ref: "#/components/schemas/Workflows"
|
|
||||||
* responses:
|
|
||||||
* 200:
|
|
||||||
* description: The items were successfully imported
|
|
||||||
* content:
|
|
||||||
* application/json:
|
|
||||||
* schema:
|
|
||||||
* $ref: "#/components/schemas/Workflows"
|
|
||||||
* 401:
|
|
||||||
* $ref: "#/components/responses/UnauthorizedError"
|
|
||||||
* 405:
|
|
||||||
* description: Invalid input data
|
|
||||||
* 500:
|
|
||||||
* description: Some server error
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
router.post(
|
|
||||||
'/bulk-import',
|
|
||||||
wrapAsync(async (req, res) => {
|
|
||||||
const referer =
|
|
||||||
req.headers.referer ||
|
|
||||||
`${req.protocol}://${req.hostname}${req.originalUrl}`;
|
|
||||||
const link = new URL(referer);
|
|
||||||
await WorkflowsService.bulkImport(req, res, true, link.host);
|
|
||||||
const payload = true;
|
|
||||||
res.status(200).send(payload);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @swagger
|
|
||||||
* /api/workflows/{id}:
|
|
||||||
* put:
|
|
||||||
* security:
|
|
||||||
* - bearerAuth: []
|
|
||||||
* tags: [Workflows]
|
|
||||||
* summary: Update the data of the selected item
|
|
||||||
* description: Update the data of the selected item
|
|
||||||
* parameters:
|
|
||||||
* - in: path
|
|
||||||
* name: id
|
|
||||||
* description: Item ID to update
|
|
||||||
* required: true
|
|
||||||
* schema:
|
|
||||||
* type: string
|
|
||||||
* requestBody:
|
|
||||||
* description: Set new item data
|
|
||||||
* required: true
|
|
||||||
* content:
|
|
||||||
* application/json:
|
|
||||||
* schema:
|
|
||||||
* properties:
|
|
||||||
* id:
|
|
||||||
* description: ID of the updated item
|
|
||||||
* type: string
|
|
||||||
* data:
|
|
||||||
* description: Data of the updated item
|
|
||||||
* type: object
|
|
||||||
* $ref: "#/components/schemas/Workflows"
|
|
||||||
* required:
|
|
||||||
* - id
|
|
||||||
* responses:
|
|
||||||
* 200:
|
|
||||||
* description: The item data was successfully updated
|
|
||||||
* content:
|
|
||||||
* application/json:
|
|
||||||
* schema:
|
|
||||||
* $ref: "#/components/schemas/Workflows"
|
|
||||||
* 400:
|
|
||||||
* description: Invalid ID supplied
|
|
||||||
* 401:
|
|
||||||
* $ref: "#/components/responses/UnauthorizedError"
|
|
||||||
* 404:
|
|
||||||
* description: Item not found
|
|
||||||
* 500:
|
|
||||||
* description: Some server error
|
|
||||||
*/
|
|
||||||
router.put(
|
|
||||||
'/:id',
|
|
||||||
wrapAsync(async (req, res) => {
|
|
||||||
await WorkflowsService.update(req.body.data, req.body.id, req.currentUser);
|
|
||||||
const payload = true;
|
|
||||||
res.status(200).send(payload);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @swagger
|
|
||||||
* /api/workflows/{id}:
|
|
||||||
* delete:
|
|
||||||
* security:
|
|
||||||
* - bearerAuth: []
|
|
||||||
* tags: [Workflows]
|
|
||||||
* summary: Delete the selected item
|
|
||||||
* description: Delete the selected item
|
|
||||||
* parameters:
|
|
||||||
* - in: path
|
|
||||||
* name: id
|
|
||||||
* description: Item ID to delete
|
|
||||||
* required: true
|
|
||||||
* schema:
|
|
||||||
* type: string
|
|
||||||
* responses:
|
|
||||||
* 200:
|
|
||||||
* description: The item was successfully deleted
|
|
||||||
* content:
|
|
||||||
* application/json:
|
|
||||||
* schema:
|
|
||||||
* $ref: "#/components/schemas/Workflows"
|
|
||||||
* 400:
|
|
||||||
* description: Invalid ID supplied
|
|
||||||
* 401:
|
|
||||||
* $ref: "#/components/responses/UnauthorizedError"
|
|
||||||
* 404:
|
|
||||||
* description: Item not found
|
|
||||||
* 500:
|
|
||||||
* description: Some server error
|
|
||||||
*/
|
|
||||||
router.delete(
|
|
||||||
'/:id',
|
|
||||||
wrapAsync(async (req, res) => {
|
|
||||||
await WorkflowsService.remove(req.params.id, req.currentUser);
|
|
||||||
const payload = true;
|
|
||||||
res.status(200).send(payload);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @swagger
|
|
||||||
* /api/workflows/deleteByIds:
|
|
||||||
* post:
|
|
||||||
* security:
|
|
||||||
* - bearerAuth: []
|
|
||||||
* tags: [Workflows]
|
|
||||||
* summary: Delete the selected item list
|
|
||||||
* description: Delete the selected item list
|
|
||||||
* requestBody:
|
|
||||||
* required: true
|
|
||||||
* content:
|
|
||||||
* application/json:
|
|
||||||
* schema:
|
|
||||||
* properties:
|
|
||||||
* ids:
|
|
||||||
* description: IDs of the updated items
|
|
||||||
* type: array
|
|
||||||
* responses:
|
|
||||||
* 200:
|
|
||||||
* description: The items was successfully deleted
|
|
||||||
* content:
|
|
||||||
* application/json:
|
|
||||||
* schema:
|
|
||||||
* $ref: "#/components/schemas/Workflows"
|
|
||||||
* 401:
|
|
||||||
* $ref: "#/components/responses/UnauthorizedError"
|
|
||||||
* 404:
|
|
||||||
* description: Items not found
|
|
||||||
* 500:
|
|
||||||
* description: Some server error
|
|
||||||
*/
|
|
||||||
router.post(
|
|
||||||
'/deleteByIds',
|
|
||||||
wrapAsync(async (req, res) => {
|
|
||||||
await WorkflowsService.deleteByIds(req.body.data, req.currentUser);
|
|
||||||
const payload = true;
|
|
||||||
res.status(200).send(payload);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @swagger
|
|
||||||
* /api/workflows:
|
|
||||||
* get:
|
|
||||||
* security:
|
|
||||||
* - bearerAuth: []
|
|
||||||
* tags: [Workflows]
|
|
||||||
* summary: Get all workflows
|
|
||||||
* description: Get all workflows
|
|
||||||
* responses:
|
|
||||||
* 200:
|
|
||||||
* description: Workflows list successfully received
|
|
||||||
* content:
|
|
||||||
* application/json:
|
|
||||||
* schema:
|
|
||||||
* type: array
|
|
||||||
* items:
|
|
||||||
* $ref: "#/components/schemas/Workflows"
|
|
||||||
* 401:
|
|
||||||
* $ref: "#/components/responses/UnauthorizedError"
|
|
||||||
* 404:
|
|
||||||
* description: Data not found
|
|
||||||
* 500:
|
|
||||||
* description: Some server error
|
|
||||||
*/
|
|
||||||
router.get(
|
|
||||||
'/',
|
|
||||||
wrapAsync(async (req, res) => {
|
|
||||||
const filetype = req.query.filetype;
|
|
||||||
|
|
||||||
const globalAccess = req.currentUser.app_role.globalAccess;
|
|
||||||
|
|
||||||
const currentUser = req.currentUser;
|
|
||||||
const payload = await WorkflowsDBApi.findAll(req.query, globalAccess, {
|
|
||||||
currentUser,
|
|
||||||
});
|
|
||||||
if (filetype && filetype === 'csv') {
|
|
||||||
const fields = ['id', 'name', 'description'];
|
|
||||||
const opts = { fields };
|
|
||||||
try {
|
|
||||||
const csv = parse(payload.rows, opts);
|
|
||||||
res.status(200).attachment(csv);
|
|
||||||
res.send(csv);
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
res.status(200).send(payload);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @swagger
|
|
||||||
* /api/workflows/count:
|
|
||||||
* get:
|
|
||||||
* security:
|
|
||||||
* - bearerAuth: []
|
|
||||||
* tags: [Workflows]
|
|
||||||
* summary: Count all workflows
|
|
||||||
* description: Count all workflows
|
|
||||||
* responses:
|
|
||||||
* 200:
|
|
||||||
* description: Workflows count successfully received
|
|
||||||
* content:
|
|
||||||
* application/json:
|
|
||||||
* schema:
|
|
||||||
* type: array
|
|
||||||
* items:
|
|
||||||
* $ref: "#/components/schemas/Workflows"
|
|
||||||
* 401:
|
|
||||||
* $ref: "#/components/responses/UnauthorizedError"
|
|
||||||
* 404:
|
|
||||||
* description: Data not found
|
|
||||||
* 500:
|
|
||||||
* description: Some server error
|
|
||||||
*/
|
|
||||||
router.get(
|
|
||||||
'/count',
|
|
||||||
wrapAsync(async (req, res) => {
|
|
||||||
const globalAccess = req.currentUser.app_role.globalAccess;
|
|
||||||
|
|
||||||
const currentUser = req.currentUser;
|
|
||||||
const payload = await WorkflowsDBApi.findAll(req.query, globalAccess, {
|
|
||||||
countOnly: true,
|
|
||||||
currentUser,
|
|
||||||
});
|
|
||||||
|
|
||||||
res.status(200).send(payload);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @swagger
|
|
||||||
* /api/workflows/autocomplete:
|
|
||||||
* get:
|
|
||||||
* security:
|
|
||||||
* - bearerAuth: []
|
|
||||||
* tags: [Workflows]
|
|
||||||
* summary: Find all workflows that match search criteria
|
|
||||||
* description: Find all workflows that match search criteria
|
|
||||||
* responses:
|
|
||||||
* 200:
|
|
||||||
* description: Workflows list successfully received
|
|
||||||
* content:
|
|
||||||
* application/json:
|
|
||||||
* schema:
|
|
||||||
* type: array
|
|
||||||
* items:
|
|
||||||
* $ref: "#/components/schemas/Workflows"
|
|
||||||
* 401:
|
|
||||||
* $ref: "#/components/responses/UnauthorizedError"
|
|
||||||
* 404:
|
|
||||||
* description: Data not found
|
|
||||||
* 500:
|
|
||||||
* description: Some server error
|
|
||||||
*/
|
|
||||||
router.get('/autocomplete', async (req, res) => {
|
|
||||||
const globalAccess = req.currentUser.app_role.globalAccess;
|
|
||||||
|
|
||||||
const organizationId = req.currentUser.organization?.id;
|
|
||||||
|
|
||||||
const payload = await WorkflowsDBApi.findAllAutocomplete(
|
|
||||||
req.query.query,
|
|
||||||
req.query.limit,
|
|
||||||
req.query.offset,
|
|
||||||
globalAccess,
|
|
||||||
organizationId,
|
|
||||||
);
|
|
||||||
|
|
||||||
res.status(200).send(payload);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @swagger
|
|
||||||
* /api/workflows/{id}:
|
|
||||||
* get:
|
|
||||||
* security:
|
|
||||||
* - bearerAuth: []
|
|
||||||
* tags: [Workflows]
|
|
||||||
* summary: Get selected item
|
|
||||||
* description: Get selected item
|
|
||||||
* parameters:
|
|
||||||
* - in: path
|
|
||||||
* name: id
|
|
||||||
* description: ID of item to get
|
|
||||||
* required: true
|
|
||||||
* schema:
|
|
||||||
* type: string
|
|
||||||
* responses:
|
|
||||||
* 200:
|
|
||||||
* description: Selected item successfully received
|
|
||||||
* content:
|
|
||||||
* application/json:
|
|
||||||
* schema:
|
|
||||||
* $ref: "#/components/schemas/Workflows"
|
|
||||||
* 400:
|
|
||||||
* description: Invalid ID supplied
|
|
||||||
* 401:
|
|
||||||
* $ref: "#/components/responses/UnauthorizedError"
|
|
||||||
* 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(
|
|
||||||
'/:id',
|
|
||||||
wrapAsync(async (req, res) => {
|
|
||||||
const payload = await WorkflowsDBApi.findBy({ id: req.params.id });
|
|
||||||
if (!payload) {
|
|
||||||
return res.status(404).json({ error: 'Not found' });
|
|
||||||
}
|
|
||||||
res.json(Helpers.sanitizeWorkflow(payload));
|
|
||||||
})
|
|
||||||
);
|
|
||||||
);
|
|
||||||
|
|
||||||
router.use('/', require('../helpers').commonErrorHandler);
|
|
||||||
|
|
||||||
module.exports = router;
|
|
||||||
@ -1 +0,0 @@
|
|||||||
{}
|
|
||||||
@ -60,15 +60,6 @@ const menuAside: MenuAsideItem[] = [
|
|||||||
: icon.mdiTable ?? icon.mdiTable,
|
: icon.mdiTable ?? icon.mdiTable,
|
||||||
permissions: 'READ_WORKFLOWS',
|
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',
|
href: '/roles/roles-list',
|
||||||
label: 'Roles',
|
label: 'Roles',
|
||||||
|
|||||||
@ -1,108 +0,0 @@
|
|||||||
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;
|
|
||||||
@ -1,112 +0,0 @@
|
|||||||
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);
|
|
||||||
|
|
||||||
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