diff --git a/.vscode/settings.json b/.vscode/settings.json index 6caeac7..c6bb237 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -16,6 +16,7 @@ "*ui.*": false }, "cSpell.words": [ + "antd", "uuidv" ] } \ No newline at end of file diff --git a/server/package.json b/server/package.json index 868174c..766580a 100644 --- a/server/package.json +++ b/server/package.json @@ -49,6 +49,6 @@ "spark-md5": "^3.0.2", "ts-md5": "^1.3.1", "tsc-watch": "^6.0.4", - "typedi": "^0.10.0" + "uuid": "^9.0.1" } } diff --git a/server/src/core/controllers/app.ts b/server/src/core/controllers/app.ts index 23edea4..795e556 100644 --- a/server/src/core/controllers/app.ts +++ b/server/src/core/controllers/app.ts @@ -1,13 +1,13 @@ import express from "express"; import { Routes } from "../interfaces/router"; import cors from "cors"; -import locator from "../di/register_di"; -import { DevEnv, UnitTestEnv } from "../di/env"; import mongoose from "mongoose"; import { Server } from "socket.io"; import { createServer } from "http"; -import { TypedEvent } from "../helper/typed_event"; import { SocketSubscriber } from "./socket_controller"; +import { dirname } from "path"; +import { CreateFolderUseCase } from "../usecases/crete_folder_usecase"; +import { dirIsExists } from "../repository/fs"; export class App { public app: express.Application; @@ -59,6 +59,7 @@ export class App { this.app.use(cors()); this.app.use(express.json()); this.app.use(express.urlencoded({ extended: true })); + this.app.use(express.static("public")); } private initializeRoutes(routes: Routes[]) { @@ -72,6 +73,7 @@ export class App { // ? new DevEnv(this.computedFolder) // : new UnitTestEnv(this.computedFolder) // ); + await this.appStartController(); mongoose .connect("mongodb://127.0.0.1:27017/test") @@ -80,4 +82,27 @@ export class App { console.log("ERROR:", e); }); } + + async appStartController() { + console.log(App.staticFilesStoreDir()); + if (await dirIsExists(App.staticFilesStoreDir())) { + return; + } + const createFolderUseCase = await new CreateFolderUseCase().call( + App.staticFilesStoreDir() + ); + + createFolderUseCase.fold( + (_s) => {}, + (e) => { + // TODO:(IDONTSUDO) need logger + console.log(e); + } + ); + } + static staticFilesStoreDir = () => { + const dir = dirname(__filename); + const rootDir = dir.slice(0, dir.length - 20); + return rootDir + "public/"; + }; } diff --git a/server/src/core/controllers/crud_controller.ts b/server/src/core/controllers/crud_controller.ts index d2ebacc..5a0862e 100644 --- a/server/src/core/controllers/crud_controller.ts +++ b/server/src/core/controllers/crud_controller.ts @@ -18,17 +18,25 @@ export class CrudController extends CoreHttpController { this.init(); } init() { - this.routes["POST"] = new CreateDataBaseModelUseCase( - this.dataBaseModel - ).call; - this.routes["GET"] = new PaginationDataBaseModelUseCase( - this.dataBaseModel - ).call; - this.routes["DELETE"] = new DeleteDataBaseModelUseCase( - this.dataBaseModel - ).call; - this.routes["PUT"] = new UpdateDataBaseModelUseCase( - this.dataBaseModel - ).call; + if (this.routes["POST"] === null) { + this.routes["POST"] = new CreateDataBaseModelUseCase( + this.dataBaseModel + ).call; + } + if (this.routes["GET"] === null) { + this.routes["GET"] = new PaginationDataBaseModelUseCase( + this.dataBaseModel + ).call; + } + if (this.routes["DELETE"] === null) { + this.routes["DELETE"] = new DeleteDataBaseModelUseCase( + this.dataBaseModel + ).call; + } + if (this.routes["PUT"] === null) { + this.routes["PUT"] = new UpdateDataBaseModelUseCase( + this.dataBaseModel + ).call; + } } } diff --git a/server/src/core/controllers/http_controller.ts b/server/src/core/controllers/http_controller.ts index 058b7d7..9767e6b 100644 --- a/server/src/core/controllers/http_controller.ts +++ b/server/src/core/controllers/http_controller.ts @@ -68,7 +68,7 @@ export class CoreHttpController implements ICoreHttpController { public delete(usecase: CallBackFunction) { this.routes["DELETE"] = usecase; } - private async requestResponseController( + public async requestResponseController( req: Request, res: Response, usecase: CallBackFunction diff --git a/server/src/core/model/pipiline_meta.ts b/server/src/core/model/pipeline_meta.ts similarity index 100% rename from server/src/core/model/pipiline_meta.ts rename to server/src/core/model/pipeline_meta.ts diff --git a/server/src/core/repository/fs.ts b/server/src/core/repository/fs.ts new file mode 100644 index 0000000..59c9c94 --- /dev/null +++ b/server/src/core/repository/fs.ts @@ -0,0 +1,29 @@ +import * as fs from "fs"; +import { promisify } from "node:util"; + +export const readFileAsync = promisify(fs.readFile); +export const readdir = promisify(fs.readdir); +export const stat = promisify(fs.stat); +export const lsStat = promisify(fs.lstat); +export const createDir = promisify(fs.mkdir); +export const dirIsExists = promisify(fs.exists); + +export function readDirRecursive(path: string, filesToDir: string[] = []) { + const files = fs.readdirSync(path); + files.forEach((file) => { + let filePath = ""; + if (path[path.length - 1] !== "/") { + filePath = `${path}/${file}`; + } else { + filePath = `${path}${file}`; + } + + const stats = fs.statSync(filePath); + if (stats.isDirectory()) { + readDirRecursive(filePath, filesToDir); + } else { + filesToDir.push(file); + } + }); + return filesToDir; +} diff --git a/server/src/core/services/files_change_notifier_service.ts b/server/src/core/services/files_change_notifier_service.ts index af0d397..eda0507 100644 --- a/server/src/core/services/files_change_notifier_service.ts +++ b/server/src/core/services/files_change_notifier_service.ts @@ -1,6 +1,6 @@ import * as fs from "fs"; import { resolve } from "node:path"; -import { promisify } from "node:util"; + import { createHash } from "node:crypto"; import "reflect-metadata"; import { BinaryLike } from "crypto"; @@ -10,11 +10,9 @@ import { } from "../model/meta_data_file_manager_model"; import { Result } from "../helper/result"; import { TypedEvent } from "../helper/typed_event"; +import { lsStat, readFileAsync, readdir, stat } from "../repository/fs"; -const readFileAsync = promisify(fs.readFile); -const readdir = promisify(fs.readdir); -const stat = promisify(fs.stat); -const lsStat = promisify(fs.lstat); + function joinBuffers(buffers: Array, delimiter = " ") { const d = Buffer.from(delimiter); @@ -34,6 +32,7 @@ async function readFileAtBuffer(path: string): Promise { } return await readFileAsync(path); } + function md5(content: Buffer | BinaryLike): Promise { return new Promise((resolve, _reject) => { return resolve(createHash("md5").update(content).digest("hex")); @@ -47,6 +46,8 @@ export interface IHashesCache { export abstract class IFilesChangeNotifierService { abstract directory: string; } + + export class FilesChangeNotifierService extends TypedEvent> implements IFilesChangeNotifierService diff --git a/server/src/core/services/pipeline_real_time_service.ts b/server/src/core/services/pipeline_real_time_service.ts index 640a705..374e732 100644 --- a/server/src/core/services/pipeline_real_time_service.ts +++ b/server/src/core/services/pipeline_real_time_service.ts @@ -1,11 +1,10 @@ import { TypedEvent } from "../helper/typed_event"; import { ExecError } from "../model/exec_error_model"; import { ExecutorResult } from "../model/executor_result"; -import { IPipelineMeta } from "../model/pipiline_meta"; +import { IPipelineMeta } from "../model/pipeline_meta"; import { IPipeline } from "../model/process_model"; import { Iteration, StackService } from "./stack_service"; - export class PipelineRealTimeService extends TypedEvent { status: IPipelineMeta; pipelineModels?: IPipeline[]; @@ -13,7 +12,7 @@ export class PipelineRealTimeService extends TypedEvent { super(); this.init(); } - init() { + private init(): void { this.status = { pipelineIsRunning: false, projectUUID: null, diff --git a/server/src/core/usecases/crete_folder_usecase.ts b/server/src/core/usecases/crete_folder_usecase.ts new file mode 100644 index 0000000..b6851a2 --- /dev/null +++ b/server/src/core/usecases/crete_folder_usecase.ts @@ -0,0 +1,17 @@ +import { Result } from "../helper/result"; +import { dirIsExists, createDir } from "../repository/fs"; + +export class CreateFolderUseCase { + call = async (path: string): Promise> => { + try { + if (await dirIsExists(path)) { + return Result.error(new Error("createFolderUseCase create dir ")); + } + await createDir(path); + + return Result.ok("ok"); + } catch (error) { + return Result.error(error as Error); + } + }; +} diff --git a/server/src/core/usecases/read_database_model_usecase.ts b/server/src/core/usecases/read_by_id_database_model_usecase.ts similarity index 100% rename from server/src/core/usecases/read_database_model_usecase.ts rename to server/src/core/usecases/read_by_id_database_model_usecase.ts diff --git a/server/src/features/projects/create_new_project_scenario.ts b/server/src/features/projects/create_new_project_scenario.ts new file mode 100644 index 0000000..b4aa832 --- /dev/null +++ b/server/src/features/projects/create_new_project_scenario.ts @@ -0,0 +1,33 @@ +import { App } from "../../core/controllers/app"; +import { Result } from "../../core/helper/result"; +import { CreateDataBaseModelUseCase } from "../../core/usecases/create_database_model_usecase"; +import { CreateFolderUseCase } from "../../core/usecases/crete_folder_usecase"; +import { ProjectDBModel, ProjectValidationModel } from "./projects_model"; +import { v4 as uuidv4 } from "uuid"; + +export class CreateNewProjectScenario { + call = async (model: ProjectValidationModel): Promise> => { + try { + const folderName = uuidv4() + "/"; + const createFolderUseCase = await new CreateFolderUseCase().call( + App.staticFilesStoreDir() + folderName + ); + if (createFolderUseCase.isFailure()) { + return createFolderUseCase.forward(); + } + + model.rootDir = folderName; + const createDataBaseModelUseCase = await new CreateDataBaseModelUseCase( + ProjectDBModel + ).call(model); + + if (createDataBaseModelUseCase.isFailure()) { + return createDataBaseModelUseCase.forward(); + } + + return Result.ok({ status: "ok" }); + } catch (error) { + return Result.error(error as Error); + } + }; +} diff --git a/server/src/features/projects/projects_model.ts b/server/src/features/projects/projects_model.ts index b359f59..5bc18f3 100644 --- a/server/src/features/projects/projects_model.ts +++ b/server/src/features/projects/projects_model.ts @@ -1,8 +1,9 @@ import { Schema, model } from "mongoose"; import { PipelineModel, schemaPipeline } from "../pipelines/pipeline_model"; -import { IsMongoId, IsString } from "class-validator"; +import { IsArray, IsOptional, IsString } from "class-validator"; export interface IProjectModel { + _id: string; pipelines: [PipelineModel]; rootDir: string; description: string; @@ -27,13 +28,11 @@ const schema = "Projects"; export const ProjectDBModel = model(schema, ProjectSchema); -export class ProjectModel implements IProjectModel { - @IsMongoId() - public pipelines: [PipelineModel]; - - @IsString() - public rootDir: string; - +export class ProjectValidationModel { + @IsArray() + public pipelines: [string]; @IsString() public description: string; + @IsOptional() + public rootDir: string; } diff --git a/server/src/features/projects/projects_presentation.ts b/server/src/features/projects/projects_presentation.ts index e9414ad..abd4b45 100644 --- a/server/src/features/projects/projects_presentation.ts +++ b/server/src/features/projects/projects_presentation.ts @@ -1,16 +1,17 @@ -// import { TriggerDBModel, TriggerModel } from "./trigger_model"; import { CrudController } from "../../core/controllers/crud_controller"; -import { ProjectDBModel, ProjectModel } from "./projects_model"; +import { CreateNewProjectScenario } from "./create_new_project_scenario"; +import { ProjectDBModel, ProjectValidationModel } from "./projects_model"; export class ProjectsPresentation extends CrudController< - ProjectModel, + ProjectValidationModel, typeof ProjectDBModel > { constructor() { super({ url: "project", - validationModel: ProjectModel, + validationModel: ProjectValidationModel, databaseModel: ProjectDBModel, }); + super.post(new CreateNewProjectScenario().call); } } diff --git a/server/src/features/realtime/realtime_presentation.ts b/server/src/features/realtime/realtime_presentation.ts index a1bbfbd..eb99350 100644 --- a/server/src/features/realtime/realtime_presentation.ts +++ b/server/src/features/realtime/realtime_presentation.ts @@ -1,29 +1,24 @@ +import { IsString } from "class-validator"; import { CoreHttpController } from "../../core/controllers/http_controller"; -import { Result } from "../../core/helper/result"; -import { IPipelineMeta } from "../../core/model/pipiline_meta"; -import { - PipelineRealTimeService, -} from "../../core/services/pipeline_real_time_service"; +import { PipelineRealTimeService } from "../../core/services/pipeline_real_time_service"; +import { RunInstancePipelineUseCase } from "./usecases/run_instance_pipeline_usecase"; +import { PipelineStatusUseCase } from "./usecases/pipeline_status_usecase"; export const pipelineRealTimeService = new PipelineRealTimeService(); -class PipelineStatusUseCase { - async call(): Promise> { - try { - return Result.ok(pipelineRealTimeService.status); - } catch (error) { - return Result.error(error as Error); - } - } +export class RealTimeValidationModel { + @IsString() + public id: string; } -export class RealTimePresentation extends CoreHttpController { +export class RealTimePresentation extends CoreHttpController { constructor() { super({ - validationModel: null, + validationModel: RealTimeValidationModel, url: "realtime", databaseModel: null, }); + super.post(new RunInstancePipelineUseCase().call); super.get(new PipelineStatusUseCase().call); } } diff --git a/server/src/features/realtime/usecases/pipeline_status_usecase.ts b/server/src/features/realtime/usecases/pipeline_status_usecase.ts new file mode 100644 index 0000000..b07870d --- /dev/null +++ b/server/src/features/realtime/usecases/pipeline_status_usecase.ts @@ -0,0 +1,13 @@ +import { Result } from "../../../core/helper/result"; +import { IPipelineMeta } from "../../../core/model/pipeline_meta"; +import { pipelineRealTimeService } from "../realtime_presentation"; + +export class PipelineStatusUseCase { + async call(): Promise> { + try { + return Result.ok(pipelineRealTimeService.status); + } catch (error) { + return Result.error(error as Error); + } + } +} diff --git a/server/src/features/realtime/usecases/run_instance_pipeline_usecase.ts b/server/src/features/realtime/usecases/run_instance_pipeline_usecase.ts new file mode 100644 index 0000000..08ea048 --- /dev/null +++ b/server/src/features/realtime/usecases/run_instance_pipeline_usecase.ts @@ -0,0 +1,30 @@ +import { Result } from "../../../core/helper/result"; +import { ReadByIdDataBaseModelUseCase } from "../../../core/usecases/read_by_id_database_model_usecase"; +import { IProjectModel, ProjectDBModel } from "../../projects/projects_model"; +import { + RealTimeValidationModel, + pipelineRealTimeService, +} from "../realtime_presentation"; + +export class RunInstancePipelineUseCase { + async call(model: RealTimeValidationModel): Promise> { + const id = model.id; + const readByIdDataBaseModelUseCase = + await new ReadByIdDataBaseModelUseCase( + ProjectDBModel + ).call(id); + if (readByIdDataBaseModelUseCase.isFailure()) { + return readByIdDataBaseModelUseCase.forward(); + } + + const projectModel = readByIdDataBaseModelUseCase.value; + + pipelineRealTimeService.runPipeline( + projectModel.pipelines, + projectModel.rootDir, + projectModel._id + ); + + return Result.ok({ status: "ok" }); + } +} diff --git a/server/src/main.ts b/server/src/main.ts index 6409186..c75c97a 100644 --- a/server/src/main.ts +++ b/server/src/main.ts @@ -1,16 +1,16 @@ import "reflect-metadata"; import { App } from "./core/controllers/app"; +import { SocketSubscriber } from "./core/controllers/socket_controller"; import { Routes } from "./core/interfaces/router"; import { TriggerPresentation } from "./features/triggers/triggers_presentation"; import { ProjectsPresentation } from "./features/projects/projects_presentation"; import { PipelinePresentation } from "./features/pipelines/pipeline_presentation"; import { ProcessPresentation } from "./features/process/process_presentation"; -import { SocketSubscriber } from "./core/controllers/socket_controller"; import { RealTimePresentation, pipelineRealTimeService, } from "./features/realtime/realtime_presentation"; - + const httpRoutes: Routes[] = [ new TriggerPresentation(), new ProjectsPresentation(), @@ -24,4 +24,4 @@ const socketSubscribers = [ ]; new App(httpRoutes, socketSubscribers).listen(); - + \ No newline at end of file diff --git a/server/test/services/pipeline_real_time_service_test.ts b/server/test/services/pipeline_real_time_service_test.ts index aa8a6ef..e39e374 100644 --- a/server/test/services/pipeline_real_time_service_test.ts +++ b/server/test/services/pipeline_real_time_service_test.ts @@ -5,7 +5,6 @@ import { dirname__ } from "../test"; export class PipelineRealTimeServiceTest extends PipelineRealTimeService { constructor() { super(); - this.init(); } async test() { this.runPipeline(mockSimplePipeline, dirname__, ""); diff --git a/server/test/services/stack_service_test.ts b/server/test/services/stack_service_test.ts index 8c3cce5..5af4a2f 100644 --- a/server/test/services/stack_service_test.ts +++ b/server/test/services/stack_service_test.ts @@ -1,34 +1,15 @@ import { rmSync } from "fs"; -import * as fs from "fs"; import { StackService } from "../../src/core/services/stack_service"; import { delay } from "../../src/core/helper/delay"; import { assert, dirname__ } from "../test"; import { mockSimplePipeline } from "../model/mock_pipelines"; +import { readDirRecursive } from "../../src/core/repository/fs"; abstract class IStackServiceTest { abstract test(): Promise; } -export function readDirRecursive(path: string, filesToDir: string[] = []) { - const files = fs.readdirSync(path); - files.forEach((file) => { - let filePath = ""; - if (path[path.length - 1] !== "/") { - filePath = `${path}/${file}`; - } else { - filePath = `${path}${file}`; - } - - const stats = fs.statSync(filePath); - if (stats.isDirectory()) { - readDirRecursive(filePath, filesToDir); - } else { - filesToDir.push(file); - } - }); - return filesToDir; -} - + class SimpleTestStackServiceTest extends StackService implements IStackServiceTest diff --git a/server/test/test.ts b/server/test/test.ts index a1056f0..87888d8 100644 --- a/server/test/test.ts +++ b/server/test/test.ts @@ -14,9 +14,8 @@ import { UpdateDataBaseModelUseCaseTest } from "./usecases/update_database_model import { PaginationDataBaseModelUseCaseTest } from "./usecases/pagination_database_model_usecase_test"; // import { PipelineRealTimeServiceTest } from "./services/pipeline_real_time_service_test"; - const testCore = TestCore.instance; - + export const dirname__: string = dirname(__filename); export const assert = testCore.assert; export const resultTest = testCore.resultTest; @@ -24,30 +23,36 @@ const env = new UnitTestEnv(dirname__); locator(env); -const tests = [CreateDataBaseModelUseCaseTest, DeleteDataBaseModelUseCaseTest,ReadDataBaseModelUseCaseTest,UpdateDataBaseModelUseCaseTest, PaginationDataBaseModelUseCaseTest] -const init = async () =>{ - await mongoose.connect('mongodb://127.0.0.1:27017/test') -} - -const test = async () =>{ - // await new ExecutorProgramServiceTest(dirname__).test(); - // await new FilesChangerTest(dirname__).test(); - await new StackServiceTest(dirname__ + "/context/").test(); - // await new TriggerServiceTest().test(); - // await new CreateDataBaseModelUseCaseTest().test() +const tests = [ + CreateDataBaseModelUseCaseTest, + DeleteDataBaseModelUseCaseTest, + ReadDataBaseModelUseCaseTest, + UpdateDataBaseModelUseCaseTest, + PaginationDataBaseModelUseCaseTest, +]; +const init = async () => { + await mongoose.connect("mongodb://127.0.0.1:27017/test"); +}; - // await new CreateDataBaseModelUseCaseTest().test() - // await new DeleteDataBaseModelUseCaseTest().test() - // await new ReadDataBaseModelUseCaseTest().test() - // await new UpdateDataBaseModelUseCaseTest().test() +const test = async () => { + await new ExecutorProgramServiceTest(dirname__).test(); + await new FilesChangerTest(dirname__).test(); + await new StackServiceTest(dirname__ + "/context/").test(); + await new TriggerServiceTest().test(); + await new CreateDataBaseModelUseCaseTest().test(); + + await new CreateDataBaseModelUseCaseTest().test(); + await new DeleteDataBaseModelUseCaseTest().test(); + await new ReadDataBaseModelUseCaseTest().test(); + await new UpdateDataBaseModelUseCaseTest().test(); // await new PipelineRealTimeServiceTest().test() - // for await (const usecase of tests) { - // testCore.assert(await new usecase().test(), usecase.name) - // } -} + for await (const usecase of tests) { + testCore.assert(await new usecase().test(), usecase.name); + } +}; const main = async () => { - await init() - await test() + await init(); + await test(); await testCore.testResult(); }; diff --git a/server/test/usecases/read_database_model_usecase_test.ts b/server/test/usecases/read_database_model_usecase_test.ts index 94fae6e..9a04be3 100644 --- a/server/test/usecases/read_database_model_usecase_test.ts +++ b/server/test/usecases/read_database_model_usecase_test.ts @@ -1,5 +1,5 @@ import { CreateDataBaseModelUseCase } from "../../src/core/usecases/create_database_model_usecase"; -import { ReadByIdDataBaseModelUseCase } from "../../src/core/usecases/read_database_model_usecase"; +import { ReadByIdDataBaseModelUseCase } from "../../src/core/usecases/read_by_id_database_model_usecase"; import { ITestModel, TestDBModel } from "../model/test_db_mongo_model"; export class ReadDataBaseModelUseCaseTest { diff --git a/ui/src/core/repository/http_repository.ts b/ui/src/core/repository/http_repository.ts index eab857b..0ebb2e7 100644 --- a/ui/src/core/repository/http_repository.ts +++ b/ui/src/core/repository/http_repository.ts @@ -1,32 +1,64 @@ +import { Result } from "../helper/result"; + export enum HttpMethod { - GET = 'GET', - POST = 'POST' + GET = "GET", + POST = "POST", } - +export class HttpError extends Error { + status: number; + error: any; + constructor(error: any, status: number) { + super(error); + this.error = error; + this.status = status; + } +} + export class HttpRepository { + private server = "http://localhost:4001"; - private server = 'http://localhost:4001' + public async jsonRequest( + method: HttpMethod, + url: string, + data?: any + ): Promise> { + try { + const reqInit = { + body: data, + method: method, + headers: { "Content-Type": "application/json" }, + }; + if (data !== undefined) { + reqInit["body"] = JSON.stringify(data); + } + const response = await fetch(this.server + url, reqInit); - public async jsonRequest(method: HttpMethod, url: string, data?: any): Promise { - const reqInit = { - 'body': data, - 'method': method, - 'headers': { 'Content-Type': 'application/json' }, - } - if (data !== undefined) { - reqInit['body'] = JSON.stringify(data) - } - return (await fetch(this.server + url, reqInit)).json() + if (response.status !== 200) { + return Result.error(new HttpError(this.server + url, response.status)); + } + + return Result.ok(await response.json()); + } catch (error) { + return Result.error(new HttpError(error, 0)); } - - public async request(method: HttpMethod, url: string, data?: any): Promise { - const reqInit = { - 'body': data, - 'method': method, - } - if (data !== undefined) { - reqInit['body'] = data - } - return (await fetch(this.server + url, reqInit)).json() + } + + public async request( + method: HttpMethod, + url: string, + data?: any + ): Promise { + const reqInit = { + body: data, + method: method, + }; + if (data !== undefined) { + reqInit["body"] = data; } -} \ No newline at end of file + const response = await fetch(this.server + url, reqInit); + if (response.status !== 200) { + throw new Error(await response.json()); + } + return response.json(); + } +} diff --git a/ui/src/core/repository/socket_repository.ts b/ui/src/core/repository/socket_repository.ts index 88f7e9f..6eda6e7 100644 --- a/ui/src/core/repository/socket_repository.ts +++ b/ui/src/core/repository/socket_repository.ts @@ -7,7 +7,7 @@ export class SocketRepository { const socket = io(this.serverURL); this.socket = socket; socket.connect(); - socket.on('mock', (d) =>{ + socket.on('realtime', (d) =>{ console.log(d) }) } diff --git a/ui/src/core/routers/routers.tsx b/ui/src/core/routers/routers.tsx index dc6add1..cd0d02e 100644 --- a/ui/src/core/routers/routers.tsx +++ b/ui/src/core/routers/routers.tsx @@ -15,6 +15,7 @@ import { CreatePipelineScreen, CreatePipelineScreenPath, } from "../../features/create_pipeline/presentation/create_pipeline_screen"; +import { CreateProjectScreen, CreateProjectScreenPath } from "../../features/create_project/create_project_screen"; export const router = createBrowserRouter([ { @@ -33,4 +34,8 @@ export const router = createBrowserRouter([ path: CreatePipelineScreenPath, element: , }, + { + path: CreateProjectScreenPath, + element: , + }, ]); diff --git a/ui/src/core/ui/pages/load_page.tsx b/ui/src/core/ui/pages/load_page.tsx index a6d3c4c..d45bd8c 100644 --- a/ui/src/core/ui/pages/load_page.tsx +++ b/ui/src/core/ui/pages/load_page.tsx @@ -12,38 +12,39 @@ interface ILoadPage extends IHeader { children?: JSX.Element | JSX.Element[]; } -export const LoadPage: React.FunctionComponent = observer(( - props: ILoadPage -) => { - return ( - <> -
- {props.isError ? ( - <> - - - not expected error - - - ) : ( - <> - )} - {props.isLoading ? ( -
- -
- ) : ( - <>{props.children} - )} - - ); -}); +export const LoadPage: React.FunctionComponent = observer( + (props: ILoadPage) => { + return ( + <> +
+ {props.isError ? ( + <> + + + not expected error + + + ) : ( + <> + {props.isLoading ? ( +
+ +
+ ) : ( + <>{props.children} + )} + + )} + + ); + } +); diff --git a/ui/src/features/all_projects/presentation/all_projects_screen.tsx b/ui/src/features/all_projects/presentation/all_projects_screen.tsx index 9032667..f62927c 100644 --- a/ui/src/features/all_projects/presentation/all_projects_screen.tsx +++ b/ui/src/features/all_projects/presentation/all_projects_screen.tsx @@ -9,8 +9,8 @@ export const AllProjectScreen: React.FunctionComponent = () => { <>
diff --git a/ui/src/features/create_pipeline/data/create_pipeline_repository.ts b/ui/src/features/create_pipeline/data/create_pipeline_repository.ts index 2d2001d..c12a8f1 100644 --- a/ui/src/features/create_pipeline/data/create_pipeline_repository.ts +++ b/ui/src/features/create_pipeline/data/create_pipeline_repository.ts @@ -5,35 +5,22 @@ import { import { ITriggerModel } from "../../../core/model/trigger_model"; import { Result } from "../../../core/helper/result"; import { IProcess } from "../../create_process/model/process_model"; -import { PipelineModelDataBase } from "../model/pipiline_model"; +import { PipelineModelDataBase } from "../model/pipeline_model"; export class CreatePipelineRepository extends HttpRepository { - async savePipeline(model: PipelineModelDataBase): Promise> { - try { - return await Result.ok( - this.jsonRequest(HttpMethod.POST, `/pipeline`, model) - ); - } catch (error) { - return Result.error(error as Error); - } + async savePipeline( + model: PipelineModelDataBase + ): Promise> { + return await this.jsonRequest(HttpMethod.POST, `/pipeline`, model); } async getTriggers(page = 1): Promise> { - try { - return Result.ok( - await this.jsonRequest(HttpMethod.GET, `/trigger?${page}`) - ); - } catch (error) { - return Result.error(error as Error); - } + return await this.jsonRequest(HttpMethod.GET, `/trigger?${page}`); } async getProcessed(page = 1): Promise> { - try { - return Result.ok( - await this.jsonRequest(HttpMethod.GET, `/process?${page}`) - ); - } catch (error) { - return Result.error(error as Error); - } + return await this.jsonRequest( + HttpMethod.GET, + `/process?${page}` + ); } } diff --git a/ui/src/features/create_pipeline/model/pipiline_model.ts b/ui/src/features/create_pipeline/model/pipeline_model.ts similarity index 100% rename from ui/src/features/create_pipeline/model/pipiline_model.ts rename to ui/src/features/create_pipeline/model/pipeline_model.ts diff --git a/ui/src/features/create_project/create_project_repository.ts b/ui/src/features/create_project/create_project_repository.ts index b2c4667..325ccfe 100644 --- a/ui/src/features/create_project/create_project_repository.ts +++ b/ui/src/features/create_project/create_project_repository.ts @@ -6,6 +6,7 @@ import { HttpRepository, } from "../../core/repository/http_repository"; import { IProcess } from "../create_process/model/process_model"; +import { ICreateProjectViewModel } from "./project_model"; export interface PipelineModel extends DatabaseModel { process: IProcess; @@ -14,12 +15,11 @@ export interface PipelineModel extends DatabaseModel { export class CreateProjectRepository extends HttpRepository { async getAllPipelines(page = 1): Promise> { - try { - return Result.ok( - await this.jsonRequest(HttpMethod.GET, "/pipeline") - ); - } catch (error) { - return Result.error(error as Error); - } + return await this.jsonRequest(HttpMethod.GET, "/pipeline"); + } + async saveProject( + model: ICreateProjectViewModel + ): Promise> { + return await this.jsonRequest(HttpMethod.POST, "/project", model); } } diff --git a/ui/src/features/create_project/create_project_screen.tsx b/ui/src/features/create_project/create_project_screen.tsx index 38d8b93..cd4522f 100644 --- a/ui/src/features/create_project/create_project_screen.tsx +++ b/ui/src/features/create_project/create_project_screen.tsx @@ -1,6 +1,89 @@ import * as React from "react"; -export const createProjectScreenPath = "/create_project"; +import { LoadPage } from "../../core/ui/pages/load_page"; +import { createProjectStore } from "./create_project_store"; +import { observer } from "mobx-react-lite"; +import { Col, Row, Input, Button } from "antd"; +import { ReactComponent as AddIcon } from "../../core/assets/icons/add.svg"; +import { CreatePipelineScreenPath } from "../create_pipeline/presentation/create_pipeline_screen"; -export const CreateProjectScreen: React.FunctionComponent = () => { - return <>; -}; +export const CreateProjectScreenPath = "/create_project"; + +export const CreateProjectScreen: React.FunctionComponent = observer(() => { + return ( + <> + + + <>Pipelines + {createProjectStore.pipelineModels?.map((el) => { + return ( +
+
{el.process.description}
+
{el.trigger.description}
+ { + createProjectStore.addPipeline(el); + }} + /> +
+ ); + })} + + + + + + createProjectStore.setDescriptionToNewProject( + e.target.value + ) + } + placeholder="project description" + /> + + + + + {createProjectStore.newProjectViews.map((el, index) => { + return ( +
+
{el.process.description}
+
{el.trigger.description}
+
{index + 1}
+
+ ); + })} + + + } + /> + + ); +}); diff --git a/ui/src/features/create_project/create_project_store.ts b/ui/src/features/create_project/create_project_store.ts index 5a0cb73..49bcf62 100644 --- a/ui/src/features/create_project/create_project_store.ts +++ b/ui/src/features/create_project/create_project_store.ts @@ -3,19 +3,26 @@ import { CreateProjectRepository, PipelineModel, } from "./create_project_repository"; +import { message } from "antd"; -class ProcessStore { +class CreateProjectStore { repository: CreateProjectRepository; isLoading = false; isError = false; pipelineModels?: PipelineModel[]; + newProjectDescription: string = ""; + newProjectViews: PipelineModel[] = []; constructor(repository: CreateProjectRepository) { this.repository = repository; makeAutoObservable(this); this.loadPipelines(); } - + + async addPipeline(model: PipelineModel) { + this.newProjectViews.push(model); + } + async loadPipelines() { this.isLoading = true; const result = await this.repository.getAllPipelines(); @@ -29,6 +36,42 @@ class ProcessStore { ); this.isLoading = false; } + + setDescriptionToNewProject(value: string): void { + this.newProjectDescription = value; + } + + async saveProject(): Promise { + if (this.newProjectDescription.isEmpty()) { + message.error("project description is not empty"); + return; + } + if (this.newProjectViews.isEmpty()) { + message.error("project view is not empty"); + return; + } + this.isLoading = true; + const result = await this.repository.saveProject({ + description: this.newProjectDescription, + pipelines: this.newProjectViews.map((el) => el._id ?? ""), + }); + + this.newProjectDescription = ""; + this.newProjectViews = []; + this.isLoading = false; + + result.fold( + (_s) => { + message.success("save"); + }, + (_e) => { + this.isError = true; + } + ); + } } -export const processStore = new ProcessStore(new CreateProjectRepository()); +export const createProjectStore = new CreateProjectStore( + new CreateProjectRepository() +); + diff --git a/ui/src/features/create_project/project_model.ts b/ui/src/features/create_project/project_model.ts new file mode 100644 index 0000000..a124f49 --- /dev/null +++ b/ui/src/features/create_project/project_model.ts @@ -0,0 +1,5 @@ +export interface ICreateProjectViewModel { + pipelines: string[]; + + description: string; +} diff --git a/ui/src/features/select_project/data/select_project_repository.ts b/ui/src/features/select_project/data/select_project_repository.ts index 84e27d5..67ee41f 100644 --- a/ui/src/features/select_project/data/select_project_repository.ts +++ b/ui/src/features/select_project/data/select_project_repository.ts @@ -7,12 +7,6 @@ import { IProjectModel } from "../model/project_model"; export class SelectProjectRepository extends HttpRepository { async getAllProjects(page = 1): Promise> { - try { - return Result.ok( - await this.jsonRequest(HttpMethod.GET, `/project?${page}`) - ); - } catch (error) { - return Result.error(error as Error); - } + return await this.jsonRequest(HttpMethod.GET, `/project?${page}`); } } diff --git a/ui/src/features/select_project/presentation/select_project.tsx b/ui/src/features/select_project/presentation/select_project.tsx index 33db1f8..29eaf7d 100644 --- a/ui/src/features/select_project/presentation/select_project.tsx +++ b/ui/src/features/select_project/presentation/select_project.tsx @@ -1,10 +1,8 @@ import * as React from "react"; import { selectProjectStore } from "./select_project_store"; -import { Loader } from "../../../core/ui/loader/loader"; import { observer } from "mobx-react-lite"; -import { Header } from "../../../core/ui/header/header"; -import { CreatePipelineScreenPath } from "../../create_pipeline/presentation/create_pipeline_screen"; import { LoadPage } from "../../../core/ui/pages/load_page"; +import { CreateProjectScreenPath } from "../../create_project/create_project_screen"; export const SelectProjectScreenPath = "/select_project"; @@ -12,7 +10,7 @@ export const SelectProjectScreen: React.FunctionComponent = observer(() => { return ( <>