From e3f294dd7f0b318dea83ec81b84087f97644bf05 Mon Sep 17 00:00:00 2001 From: Geonhyuk Seo Date: Thu, 28 Nov 2024 05:42:45 +0900 Subject: [PATCH 001/124] =?UTF-8?q?=E2=9C=85=20Test(server):=20=EC=84=9C?= =?UTF-8?q?=EB=B2=84=20=EA=B0=80=EA=B2=A9=20=EC=A0=95=EB=B3=B4=20=EB=B6=88?= =?UTF-8?q?=EB=9F=AC=EC=98=A4=EA=B8=B0=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ISSUES CLOSED: #140 --- apps/server/src/main.ts | 48 +++++++++++++++++-- .../ncloud-sdk/src/services/price/PriceApi.ts | 12 +++-- packages/ncloud-sdk/src/signature.ts | 1 - 3 files changed, 53 insertions(+), 8 deletions(-) diff --git a/apps/server/src/main.ts b/apps/server/src/main.ts index 61ba2289..7a0c1c28 100644 --- a/apps/server/src/main.ts +++ b/apps/server/src/main.ts @@ -11,9 +11,51 @@ async function bootstrap() { const app = await NestFactory.create(AppModule); const ncloud = new Ncloud(); const priceApi = new PriceApi(ncloud.keys() as ApiKeyCredentials); - console.log(priceApi); - const result = await priceApi.getProductCategoryList({}); - console.log(result.getProductCategoryListResponse.productCategoryList); + const result1 = await priceApi.getProductCategoryList({}); + console.log(result1.productCategoryList); + + // VPC + // const result = await priceApi.getProductList({ + // regionCode: 'KR', + // productCategoryCode: 'NETWORKING' + // }); + // result.productList.forEach((product) => { + // if(product.productCode === 'VPC.V001' || product.productCode === 'SPNET00000000015') + // console.log(product); + // }) + + // SERVER + // const result = await priceApi.getProductList({ + // regionCode: 'KR', + // productCategoryCode: 'COMPUTE', + // }); + // result.productList.forEach((product) => { + // if(product.productItemKind.codeName === 'Server' && product.serverProductCode){ + // console.log(product); + // } + // }) + + // SERVER PRICE + const result = await priceApi.getProductPriceList({ + regionCode: 'KR', + payCurrencyCode: 'KRW', + productCategoryCode: 'COMPUTE', + }); + result.productPriceList.forEach((product) => { + if ( + product.productItemKind.code === 'SVR' && + product.serverProductCode && + product.serverProductCode.endsWith('50') + ) { + console.log(product.serverProductCode); + console.log('월'); + console.log(product.priceList[0].price); + console.log('시간'); + console.log(product.priceList[1].price); + console.log('gksk'); + } + }); + swaggerConfig(app); app.use(helmet()); diff --git a/packages/ncloud-sdk/src/services/price/PriceApi.ts b/packages/ncloud-sdk/src/services/price/PriceApi.ts index 3ba3930b..7a761626 100644 --- a/packages/ncloud-sdk/src/services/price/PriceApi.ts +++ b/packages/ncloud-sdk/src/services/price/PriceApi.ts @@ -18,38 +18,42 @@ export class PriceApi { } async getPriceList(getPriceRequest: GetPriceListRequest) { - return await this.client.request({ + const response = await this.client.request({ method: 'POST', url: this.resourcePath + '/product/getPriceList', params: getPriceRequest, }); + return response.getPriceListResponse; } async getProductCategoryList( getProductCategoryListRequest: GetProductCategoryListRequest, ) { - return await this.client.request({ + const response = await this.client.request({ method: 'POST', url: this.resourcePath + '/product/getProductCategoryList', params: getProductCategoryListRequest, }); + return response.getProductCategoryListResponse; } async getProductList(getProductListRequest: GetProductListRequest) { - return await this.client.request({ + const response = await this.client.request({ method: 'POST', url: this.resourcePath + '/product/getProductList', params: getProductListRequest, }); + return response.getProductListResponse; } async getProductPriceList( getProductPriceListRequest: GetProductPriceListRequest, ) { - return await this.client.request({ + const response = await this.client.request({ method: 'POST', url: this.resourcePath + '/product/getProductPriceList', params: getProductPriceListRequest, }); + return response.getProductPriceListResponse; } } diff --git a/packages/ncloud-sdk/src/signature.ts b/packages/ncloud-sdk/src/signature.ts index 9a365db2..13239abe 100644 --- a/packages/ncloud-sdk/src/signature.ts +++ b/packages/ncloud-sdk/src/signature.ts @@ -19,7 +19,6 @@ export function generateSignature({ ? `${url}?${new URLSearchParams(params).toString()}` : url; - console.log(fullUrl); const message = [ method, space, From 3db95e77ec303a19a486b357478a00e6dfa89604 Mon Sep 17 00:00:00 2001 From: Geonhyuk Seo Date: Thu, 28 Nov 2024 11:00:50 +0900 Subject: [PATCH 002/124] =?UTF-8?q?=E2=9C=A8=20Feat(server):=20ncloud=20Se?= =?UTF-8?q?rver=20=EB=A6=AC=EC=86=8C=EC=8A=A4=EB=A5=BC=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=ED=95=98=EA=B8=B0=20=EC=9C=84=ED=95=9C=20=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=EB=B2=A0=EC=9D=B4=EC=8A=A4=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ISSUES CLOSED: #140 --- .../migration.sql | 24 +++++++++++++++++++ apps/server/prisma/schema/import.prisma | 16 ++++++------- .../schema/ncloud-server-resoruce.prisma | 13 ++++++++++ .../schema/ncloud-server-resource-type.prisma | 8 +++++++ .../prisma/schema/private-architecture.prisma | 20 ++++++++-------- .../schema/public-architecture-tag.prisma | 14 +++++------ .../prisma/schema/public-architecture.prisma | 22 ++++++++--------- apps/server/prisma/schema/schema.prisma | 10 ++++---- apps/server/prisma/schema/star.prisma | 16 ++++++------- apps/server/prisma/schema/tag.prisma | 10 ++++---- apps/server/prisma/schema/user.prisma | 16 ++++++------- apps/server/prisma/schema/version.prisma | 16 ++++++------- apps/server/prisma/seed.js | 6 ++--- apps/server/src/main.ts | 1 + 14 files changed, 119 insertions(+), 73 deletions(-) rename apps/server/prisma/migrations/{20241125182942_init => 20241128015739_}/migration.sql (83%) create mode 100644 apps/server/prisma/schema/ncloud-server-resoruce.prisma create mode 100644 apps/server/prisma/schema/ncloud-server-resource-type.prisma diff --git a/apps/server/prisma/migrations/20241125182942_init/migration.sql b/apps/server/prisma/migrations/20241128015739_/migration.sql similarity index 83% rename from apps/server/prisma/migrations/20241125182942_init/migration.sql rename to apps/server/prisma/migrations/20241128015739_/migration.sql index a4abcef0..e50458f7 100644 --- a/apps/server/prisma/migrations/20241125182942_init/migration.sql +++ b/apps/server/prisma/migrations/20241128015739_/migration.sql @@ -9,6 +9,27 @@ CREATE TABLE `import` ( PRIMARY KEY (`id`) ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +-- CreateTable +CREATE TABLE `ncloud_server_resource` ( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `server_resource_type_id` INTEGER NOT NULL, + `server_spec_code` CHAR(50) NOT NULL, + `hour_cost` DOUBLE NOT NULL, + `month_cost` DOUBLE NOT NULL, + `created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0), + `updated_at` TIMESTAMP(0) NULL, + + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `ncloud_server_resource_type` ( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `type` CHAR(50) NOT NULL, + + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + -- CreateTable CREATE TABLE `private_architecture` ( `id` INTEGER NOT NULL AUTO_INCREMENT, @@ -91,6 +112,9 @@ ALTER TABLE `import` ADD CONSTRAINT `import_public_architecture_id_fkey` FOREIGN -- AddForeignKey ALTER TABLE `import` ADD CONSTRAINT `import_user_id_fkey` FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; +-- AddForeignKey +ALTER TABLE `ncloud_server_resource` ADD CONSTRAINT `ncloud_server_resource_server_resource_type_id_fkey` FOREIGN KEY (`server_resource_type_id`) REFERENCES `ncloud_server_resource_type`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + -- AddForeignKey ALTER TABLE `private_architecture` ADD CONSTRAINT `private_architecture_author_id_fkey` FOREIGN KEY (`author_id`) REFERENCES `user`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/apps/server/prisma/schema/import.prisma b/apps/server/prisma/schema/import.prisma index 88c57513..25c94406 100644 --- a/apps/server/prisma/schema/import.prisma +++ b/apps/server/prisma/schema/import.prisma @@ -1,12 +1,12 @@ model Import { - id Int @id @default(autoincrement()) - publicArchitectureId Int @map("public_architecture_id") - userId Int @map("user_id") - createdAt DateTime @default(now()) @map("created_at") @db.Timestamp(0) + id Int @id @default(autoincrement()) + publicArchitectureId Int @map("public_architecture_id") + userId Int @map("user_id") + createdAt DateTime @default(now()) @map("created_at") @db.Timestamp(0) - publicArchitecture PublicArchitecture @relation(fields: [publicArchitectureId], references: [id]) - user User @relation(fields: [userId], references: [id]) + publicArchitecture PublicArchitecture @relation(fields: [publicArchitectureId], references: [id]) + user User @relation(fields: [userId], references: [id]) - @@unique([publicArchitectureId, userId], name: "unique_import") - @@map("import") + @@unique([publicArchitectureId, userId], name: "unique_import") + @@map("import") } diff --git a/apps/server/prisma/schema/ncloud-server-resoruce.prisma b/apps/server/prisma/schema/ncloud-server-resoruce.prisma new file mode 100644 index 00000000..f9d09b6d --- /dev/null +++ b/apps/server/prisma/schema/ncloud-server-resoruce.prisma @@ -0,0 +1,13 @@ +model NcloudServerResource { + id Int @id @default(autoincrement()) + serverResourceTypeId Int @map("server_resource_type_id") + serverSpecCode String @map("server_spec_code") @db.Char(50) + hourCost Float @map("hour_cost") + timeCost Float @map("month_cost") + createdAt DateTime @default(now()) @map("created_at") @db.Timestamp(0) + updatedAt DateTime? @updatedAt @map("updated_at") @db.Timestamp(0) + + serverResoruceType NcloudServerResourceType @relation(fields: [serverResourceTypeId], references: [id]) + + @@map("ncloud_server_resource") +} diff --git a/apps/server/prisma/schema/ncloud-server-resource-type.prisma b/apps/server/prisma/schema/ncloud-server-resource-type.prisma new file mode 100644 index 00000000..dddfa9c0 --- /dev/null +++ b/apps/server/prisma/schema/ncloud-server-resource-type.prisma @@ -0,0 +1,8 @@ +model NcloudServerResourceType { + id Int @id @default(autoincrement()) + type String @db.Char(50) + + NcloudServerResources NcloudServerResource[] + + @@map("ncloud_server_resource_type") +} diff --git a/apps/server/prisma/schema/private-architecture.prisma b/apps/server/prisma/schema/private-architecture.prisma index ab575cb0..cdf05778 100644 --- a/apps/server/prisma/schema/private-architecture.prisma +++ b/apps/server/prisma/schema/private-architecture.prisma @@ -1,14 +1,14 @@ model PrivateArchitecture { - id Int @id @default(autoincrement()) - title String @db.Char(50) - authorId Int @map("author_id") - architecture Json - cost Float @default(0) - createdAt DateTime @default(now()) @map("created_at") @db.Timestamp(0) - updatedAt DateTime? @updatedAt @map("updated_at") @db.Timestamp(0) + id Int @id @default(autoincrement()) + title String @db.Char(50) + authorId Int @map("author_id") + architecture Json + cost Float @default(0) + createdAt DateTime @default(now()) @map("created_at") @db.Timestamp(0) + updatedAt DateTime? @updatedAt @map("updated_at") @db.Timestamp(0) - author User @relation(fields: [authorId], references: [id]) - versions Version[] + author User @relation(fields: [authorId], references: [id]) + versions Version[] - @@map("private_architecture") + @@map("private_architecture") } diff --git a/apps/server/prisma/schema/public-architecture-tag.prisma b/apps/server/prisma/schema/public-architecture-tag.prisma index 3f704d99..b0a9987b 100644 --- a/apps/server/prisma/schema/public-architecture-tag.prisma +++ b/apps/server/prisma/schema/public-architecture-tag.prisma @@ -1,11 +1,11 @@ model PublicArchitectureTag { - id Int @id @default(autoincrement()) - publicArchitectureId Int @map("public_architecture_id") - tagId Int @map("tag_id") + id Int @id @default(autoincrement()) + publicArchitectureId Int @map("public_architecture_id") + tagId Int @map("tag_id") - publicArchitecture PublicArchitecture @relation(fields: [publicArchitectureId], references: [id]) - tag Tag @relation(fields: [tagId], references: [id]) + publicArchitecture PublicArchitecture @relation(fields: [publicArchitectureId], references: [id]) + tag Tag @relation(fields: [tagId], references: [id]) - @@unique([publicArchitectureId, tagId], name: "unique_tag") - @@map("public_architecture_tag") + @@unique([publicArchitectureId, tagId], name: "unique_tag") + @@map("public_architecture_tag") } diff --git a/apps/server/prisma/schema/public-architecture.prisma b/apps/server/prisma/schema/public-architecture.prisma index 6e603503..1d191c2c 100644 --- a/apps/server/prisma/schema/public-architecture.prisma +++ b/apps/server/prisma/schema/public-architecture.prisma @@ -1,15 +1,15 @@ model PublicArchitecture { - id Int @id @default(autoincrement()) - title String @db.Char(50) - authorId Int @map("author_id") - architecture Json - createdAt DateTime @default(now()) @map("created_at") @db.Timestamp(0) - cost Float @default(0) + id Int @id @default(autoincrement()) + title String @db.Char(50) + authorId Int @map("author_id") + architecture Json + createdAt DateTime @default(now()) @map("created_at") @db.Timestamp(0) + cost Float @default(0) - author User @relation(fields: [authorId], references: [id]) - stars Star[] - imports Import[] - PublicArchitectureTag PublicArchitectureTag[] + author User @relation(fields: [authorId], references: [id]) + stars Star[] + imports Import[] + PublicArchitectureTag PublicArchitectureTag[] - @@map("public_architecture") + @@map("public_architecture") } diff --git a/apps/server/prisma/schema/schema.prisma b/apps/server/prisma/schema/schema.prisma index a6740979..fb9e2419 100644 --- a/apps/server/prisma/schema/schema.prisma +++ b/apps/server/prisma/schema/schema.prisma @@ -1,10 +1,10 @@ generator client { - provider = "prisma-client-js" - previewFeatures = ["prismaSchemaFolder"] - binaryTargets = ["native", "linux-musl-arm64-openssl-3.0.x"] + provider = "prisma-client-js" + previewFeatures = ["prismaSchemaFolder"] + binaryTargets = ["native", "linux-musl-arm64-openssl-3.0.x"] } datasource db { - provider = "mysql" - url = env("DATABASE_URL") + provider = "mysql" + url = env("DATABASE_URL") } diff --git a/apps/server/prisma/schema/star.prisma b/apps/server/prisma/schema/star.prisma index ab0374b8..c0bf1dac 100644 --- a/apps/server/prisma/schema/star.prisma +++ b/apps/server/prisma/schema/star.prisma @@ -1,12 +1,12 @@ model Star { - id Int @id @default(autoincrement()) - publicArchitectureId Int @map("public_architecture_id") - userId Int @map("user_id") - createdAt DateTime @default(now()) @map("created_at") @db.Timestamp(0) + id Int @id @default(autoincrement()) + publicArchitectureId Int @map("public_architecture_id") + userId Int @map("user_id") + createdAt DateTime @default(now()) @map("created_at") @db.Timestamp(0) - publicArchitecture PublicArchitecture @relation(fields: [publicArchitectureId], references: [id]) - user User @relation(fields: [userId], references: [id]) + publicArchitecture PublicArchitecture @relation(fields: [publicArchitectureId], references: [id]) + user User @relation(fields: [userId], references: [id]) - @@unique([publicArchitectureId, userId], name: "unique_star") - @@map("star") + @@unique([publicArchitectureId, userId], name: "unique_star") + @@map("star") } diff --git a/apps/server/prisma/schema/tag.prisma b/apps/server/prisma/schema/tag.prisma index d50ce89f..6590a782 100644 --- a/apps/server/prisma/schema/tag.prisma +++ b/apps/server/prisma/schema/tag.prisma @@ -1,8 +1,8 @@ model Tag { - id Int @id @default(autoincrement()) - name String @db.VarChar(15) - createdAt DateTime @default(now()) @map("created_at") @db.Timestamp(0) - PublicArchitectureTag PublicArchitectureTag[] + id Int @id @default(autoincrement()) + name String @db.VarChar(15) + createdAt DateTime @default(now()) @map("created_at") @db.Timestamp(0) + PublicArchitectureTag PublicArchitectureTag[] - @@map("tag") + @@map("tag") } diff --git a/apps/server/prisma/schema/user.prisma b/apps/server/prisma/schema/user.prisma index 2822c48e..1f0737cc 100644 --- a/apps/server/prisma/schema/user.prisma +++ b/apps/server/prisma/schema/user.prisma @@ -1,12 +1,12 @@ model User { - id Int @id @default(autoincrement()) - name String @db.VarChar(30) - createdAt DateTime @default(now()) @map("created_at") @db.Timestamp(0) + id Int @id @default(autoincrement()) + name String @db.VarChar(30) + createdAt DateTime @default(now()) @map("created_at") @db.Timestamp(0) - privateArchitectures PrivateArchitecture[] - publicArchitectures PublicArchitecture[] - stars Star[] - imports Import[] + privateArchitectures PrivateArchitecture[] + publicArchitectures PublicArchitecture[] + stars Star[] + imports Import[] - @@map("user") + @@map("user") } diff --git a/apps/server/prisma/schema/version.prisma b/apps/server/prisma/schema/version.prisma index f1ffa308..d6f79213 100644 --- a/apps/server/prisma/schema/version.prisma +++ b/apps/server/prisma/schema/version.prisma @@ -1,12 +1,12 @@ model Version { - id Int @id @default(autoincrement()) - privateArchitectureId Int @map("private_architecture_id") - title String @db.Char(50) - createdAt DateTime @default(now()) @map("created_at") @db.Timestamp(0) - architecture Json - cost Float @default(0) + id Int @id @default(autoincrement()) + privateArchitectureId Int @map("private_architecture_id") + title String @db.Char(50) + createdAt DateTime @default(now()) @map("created_at") @db.Timestamp(0) + architecture Json + cost Float @default(0) - privateArchitecture PrivateArchitecture @relation(fields: [privateArchitectureId], references: [id]) + privateArchitecture PrivateArchitecture @relation(fields: [privateArchitectureId], references: [id]) - @@map("version") + @@map("version") } diff --git a/apps/server/prisma/seed.js b/apps/server/prisma/seed.js index ba173c62..3d1f56bf 100644 --- a/apps/server/prisma/seed.js +++ b/apps/server/prisma/seed.js @@ -1,7 +1,7 @@ -const PrismaClient = require('@prisma/client'); -const { faker } = require('@faker-js/faker'); +import { PrismaClient } from '@prisma/client'; +import { faker } from '@faker-js/faker'; -const prisma = new PrismaClient.PrismaClient(); +const prisma = new PrismaClient(); async function main() { const userAmount = 10; const users = []; diff --git a/apps/server/src/main.ts b/apps/server/src/main.ts index 7a0c1c28..ed138982 100644 --- a/apps/server/src/main.ts +++ b/apps/server/src/main.ts @@ -47,6 +47,7 @@ async function bootstrap() { product.serverProductCode && product.serverProductCode.endsWith('50') ) { + console.log(product.productType.codeName); console.log(product.serverProductCode); console.log('월'); console.log(product.priceList[0].price); From cfc785fbed5a287a0a691e9d1ba90d4c4b7842aa Mon Sep 17 00:00:00 2001 From: paulcjy Date: Thu, 28 Nov 2024 12:07:22 +0900 Subject: [PATCH 003/124] =?UTF-8?q?=F0=9F=A4=96=20Refactor(server):=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=EB=B2=A0=EC=9D=B4=EC=8A=A4=20?= =?UTF-8?q?=EC=8A=A4=ED=82=A4=EB=A7=88=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/server/prisma/schema/import.prisma | 1 - apps/server/prisma/schema/public-architecture.prisma | 8 ++++---- apps/server/prisma/schema/tag.prisma | 8 ++++---- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/apps/server/prisma/schema/import.prisma b/apps/server/prisma/schema/import.prisma index 88c57513..000d21f1 100644 --- a/apps/server/prisma/schema/import.prisma +++ b/apps/server/prisma/schema/import.prisma @@ -7,6 +7,5 @@ model Import { publicArchitecture PublicArchitecture @relation(fields: [publicArchitectureId], references: [id]) user User @relation(fields: [userId], references: [id]) - @@unique([publicArchitectureId, userId], name: "unique_import") @@map("import") } diff --git a/apps/server/prisma/schema/public-architecture.prisma b/apps/server/prisma/schema/public-architecture.prisma index 6e603503..fef8534f 100644 --- a/apps/server/prisma/schema/public-architecture.prisma +++ b/apps/server/prisma/schema/public-architecture.prisma @@ -6,10 +6,10 @@ model PublicArchitecture { createdAt DateTime @default(now()) @map("created_at") @db.Timestamp(0) cost Float @default(0) - author User @relation(fields: [authorId], references: [id]) - stars Star[] - imports Import[] - PublicArchitectureTag PublicArchitectureTag[] + author User @relation(fields: [authorId], references: [id]) + stars Star[] + imports Import[] + tags PublicArchitectureTag[] @@map("public_architecture") } diff --git a/apps/server/prisma/schema/tag.prisma b/apps/server/prisma/schema/tag.prisma index d50ce89f..e9d63c3f 100644 --- a/apps/server/prisma/schema/tag.prisma +++ b/apps/server/prisma/schema/tag.prisma @@ -1,8 +1,8 @@ model Tag { - id Int @id @default(autoincrement()) - name String @db.VarChar(15) - createdAt DateTime @default(now()) @map("created_at") @db.Timestamp(0) - PublicArchitectureTag PublicArchitectureTag[] + id Int @id @default(autoincrement()) + name String @unique @db.VarChar(15) + createdAt DateTime @default(now()) @map("created_at") @db.Timestamp(0) + publicArchitectures PublicArchitectureTag[] @@map("tag") } From db080f7866146d5aabb07fe4b8694d0f123a5319 Mon Sep 17 00:00:00 2001 From: Geonhyuk Seo Date: Thu, 28 Nov 2024 12:29:11 +0900 Subject: [PATCH 004/124] =?UTF-8?q?=F0=9F=9A=9A=20Setting(server):=20sched?= =?UTF-8?q?ule=20module=20=EC=84=A4=EC=B9=98=20=EB=B0=8F=20ncloud-resource?= =?UTF-8?q?s=20=EB=AA=A8=EB=93=88=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ISSUES CLOSED: #140 --- apps/server/package.json | 1 + apps/server/src/app.module.ts | 5 ++++- .../ncloud-resources.service.spec.ts | 18 ++++++++++++++++++ .../ncloud-resources.service.ts | 4 ++++ 4 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 apps/server/src/ncloud-resources/ncloud-resources.service.spec.ts create mode 100644 apps/server/src/ncloud-resources/ncloud-resources.service.ts diff --git a/apps/server/package.json b/apps/server/package.json index 5bb0d10a..ac6ae86e 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -33,6 +33,7 @@ "@nestjs/mapped-types": "*", "@nestjs/passport": "^10.0.3", "@nestjs/platform-express": "^10.0.0", + "@nestjs/schedule": "^4.1.1", "@nestjs/swagger": "^8.0.5", "@prisma/client": "^5.22.0", "class-transformer": "^0.5.1", diff --git a/apps/server/src/app.module.ts b/apps/server/src/app.module.ts index 8a4d8543..73759cf2 100644 --- a/apps/server/src/app.module.ts +++ b/apps/server/src/app.module.ts @@ -9,6 +9,8 @@ import { PublicArchitectureModule } from './public-architecture/public-architect import { PrismaService } from './prisma/prisma.service.js'; // import { routes } from './routes'; import { MyModule } from './my/my.module.js'; +import { ScheduleModule } from '@nestjs/schedule'; +import { NcloudResourcesService } from './ncloud-resources/ncloud-resources.service.js'; @Module({ imports: [ @@ -18,8 +20,9 @@ import { MyModule } from './my/my.module.js'; PublicArchitectureModule, // PrivateArchitectureModule, MyModule, + ScheduleModule.forRoot(), ], controllers: [AppController], - providers: [AppService, PrismaService], + providers: [AppService, PrismaService, NcloudResourcesService], }) export class AppModule {} diff --git a/apps/server/src/ncloud-resources/ncloud-resources.service.spec.ts b/apps/server/src/ncloud-resources/ncloud-resources.service.spec.ts new file mode 100644 index 00000000..760db37d --- /dev/null +++ b/apps/server/src/ncloud-resources/ncloud-resources.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { NcloudResourcesService } from './ncloud-resources.service.js'; + +describe('NcloudResourcesService', () => { + let service: NcloudResourcesService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [NcloudResourcesService], + }).compile(); + + service = module.get(NcloudResourcesService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/apps/server/src/ncloud-resources/ncloud-resources.service.ts b/apps/server/src/ncloud-resources/ncloud-resources.service.ts new file mode 100644 index 00000000..227d0c88 --- /dev/null +++ b/apps/server/src/ncloud-resources/ncloud-resources.service.ts @@ -0,0 +1,4 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class NcloudResourcesService {} From c8d71818903cc9d5694765dfc18edb8129f03c7d Mon Sep 17 00:00:00 2001 From: paulcjy Date: Thu, 28 Nov 2024 12:30:31 +0900 Subject: [PATCH 005/124] =?UTF-8?q?=E2=9C=A8=20Feat(server):=20optional=20?= =?UTF-8?q?auth=20guard=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/server/src/guards/optional-auth.guard.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 apps/server/src/guards/optional-auth.guard.ts diff --git a/apps/server/src/guards/optional-auth.guard.ts b/apps/server/src/guards/optional-auth.guard.ts new file mode 100644 index 00000000..66f2b99e --- /dev/null +++ b/apps/server/src/guards/optional-auth.guard.ts @@ -0,0 +1,15 @@ +import { ExecutionContext, Injectable } from '@nestjs/common'; +import { AuthGuard } from '@nestjs/passport'; + +@Injectable() +export class OptionalAuthGuard extends AuthGuard('jwt') { + handleRequest( + err: any, + user: any, + info: any, + context: ExecutionContext, + status?: any, + ): TUser { + return user; + } +} From f31c17da903f86d87e90bbb0239f2273ff59f639 Mon Sep 17 00:00:00 2001 From: paulcjy Date: Thu, 28 Nov 2024 12:31:27 +0900 Subject: [PATCH 006/124] =?UTF-8?q?=F0=9F=90=9E=20Fix(server):=20user=20?= =?UTF-8?q?=EB=8D=B0=EC=BD=94=EB=A0=88=EC=9D=B4=ED=84=B0=EC=97=90=EC=84=9C?= =?UTF-8?q?=20=EC=9C=A0=EC=A0=80=20=EA=B0=9D=EC=B2=B4=EA=B0=80=20=EC=97=86?= =?UTF-8?q?=EB=8A=94=20=EA=B2=BD=EC=9A=B0=EB=8F=84=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EC=97=86=EC=9D=B4=20=EC=9E=91=EB=8F=99=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/server/src/decorators/user.decorator.ts | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/apps/server/src/decorators/user.decorator.ts b/apps/server/src/decorators/user.decorator.ts index 2a1c7c35..b9b12c33 100644 --- a/apps/server/src/decorators/user.decorator.ts +++ b/apps/server/src/decorators/user.decorator.ts @@ -1,9 +1,4 @@ -import { - createParamDecorator, - ExecutionContext, - InternalServerErrorException, - UnauthorizedException, -} from '@nestjs/common'; +import { createParamDecorator, ExecutionContext } from '@nestjs/common'; import { User as UserEntity } from '@prisma/client'; export const User = createParamDecorator( @@ -11,14 +6,6 @@ export const User = createParamDecorator( const request = ctx.switchToHttp().getRequest(); const user = request.user; - if (!user) { - throw new UnauthorizedException('사용자 정보가 없습니다.'); - } - - if (!data) return user; - if (data in user) return user[data]; - throw new InternalServerErrorException( - `사용자 정보에 ${data}이/가 없습니다.`, - ); + return data ? user?.[data] : user; }, ); From a02390484f65bef79e44f65859557ea86db86137 Mon Sep 17 00:00:00 2001 From: paulcjy Date: Thu, 28 Nov 2024 12:34:21 +0900 Subject: [PATCH 007/124] =?UTF-8?q?=F0=9F=90=9E=20Fix(server):=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EC=8B=9C=20=ED=86=A0=ED=81=B0=EC=9D=84=20?= =?UTF-8?q?Promise=EB=A1=9C=20=EB=A6=AC=ED=84=B4=ED=95=98=EC=97=AC=20?= =?UTF-8?q?=EC=A0=9C=EB=8C=80=EB=A1=9C=20=EC=9E=91=EB=8F=99=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/server/src/auth/auth.controller.ts | 4 ++-- apps/server/src/auth/auth.service.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/server/src/auth/auth.controller.ts b/apps/server/src/auth/auth.controller.ts index 777dc7ba..4cb2fca0 100644 --- a/apps/server/src/auth/auth.controller.ts +++ b/apps/server/src/auth/auth.controller.ts @@ -17,8 +17,8 @@ export class AuthController { constructor(private readonly authService: AuthService) {} @Post('login') - login(@Res({ passthrough: true }) res: Response) { - const token = this.authService.login(); + async login(@Res({ passthrough: true }) res: Response) { + const token = await this.authService.login(); res.cookie('Authentication', token, { httpOnly: true, secure: true, diff --git a/apps/server/src/auth/auth.service.ts b/apps/server/src/auth/auth.service.ts index 1a7fe858..4a4b7c67 100644 --- a/apps/server/src/auth/auth.service.ts +++ b/apps/server/src/auth/auth.service.ts @@ -10,7 +10,7 @@ export class AuthService { ) {} async login() { - const user = this.userService.getTestUser(); + const user = await this.userService.getTestUser(); if (!user) { throw new UnauthorizedException(); } From a134701f98d20f190321033ac0a4604a8e3f0529 Mon Sep 17 00:00:00 2001 From: paulcjy Date: Thu, 28 Nov 2024 12:40:36 +0900 Subject: [PATCH 008/124] =?UTF-8?q?=E2=9C=A8=20Feat(server):=20my=20page?= =?UTF-8?q?=20=EA=B4=80=EB=A0=A8=20API=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/server/src/my/dto/create-my.dto.ts | 1 - .../src/my/dto/find-my-architectures.dto.ts | 8 ++ apps/server/src/my/dto/update-my.dto.ts | 4 - apps/server/src/my/my.controller.ts | 34 ++++- apps/server/src/my/my.service.ts | 128 ++++++++++++++---- 5 files changed, 135 insertions(+), 40 deletions(-) delete mode 100644 apps/server/src/my/dto/create-my.dto.ts create mode 100644 apps/server/src/my/dto/find-my-architectures.dto.ts delete mode 100644 apps/server/src/my/dto/update-my.dto.ts diff --git a/apps/server/src/my/dto/create-my.dto.ts b/apps/server/src/my/dto/create-my.dto.ts deleted file mode 100644 index 5f3779b9..00000000 --- a/apps/server/src/my/dto/create-my.dto.ts +++ /dev/null @@ -1 +0,0 @@ -export class CreateMyDto {} diff --git a/apps/server/src/my/dto/find-my-architectures.dto.ts b/apps/server/src/my/dto/find-my-architectures.dto.ts new file mode 100644 index 00000000..9cf6780d --- /dev/null +++ b/apps/server/src/my/dto/find-my-architectures.dto.ts @@ -0,0 +1,8 @@ +export interface FindMyArchitecturesDto { + page?: number; + limit?: number; + search?: string; + sort?: string; + order?: string; + userId: number; +} diff --git a/apps/server/src/my/dto/update-my.dto.ts b/apps/server/src/my/dto/update-my.dto.ts deleted file mode 100644 index cb15d36c..00000000 --- a/apps/server/src/my/dto/update-my.dto.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { PartialType } from '@nestjs/swagger'; -import { CreateMyDto } from './create-my.dto'; - -export class UpdateMyDto extends PartialType(CreateMyDto) {} diff --git a/apps/server/src/my/my.controller.ts b/apps/server/src/my/my.controller.ts index 77cd1b22..0d3185bc 100644 --- a/apps/server/src/my/my.controller.ts +++ b/apps/server/src/my/my.controller.ts @@ -1,23 +1,43 @@ -import { Controller, Get, Query } from '@nestjs/common'; +import { Controller, Get, Query, UseGuards } from '@nestjs/common'; import { MyService } from './my.service'; import { QueryParamsDto } from 'src/types/query-params.dto'; +import { JwtAuthGuard } from 'src/guards/jwt-auth.guard'; +import { User } from 'src/decorators/user.decorator'; @Controller('my') export class MyController { constructor(private readonly service: MyService) {} @Get('private-architectures') - getMyPrivateArchitectures(@Query() queryParams: QueryParamsDto) { - return this.service.findMyPrivateArchitectures(queryParams); + @UseGuards(JwtAuthGuard) + getMyPrivateArchitectures( + @User('id') userId: number, + @Query() queryParams: QueryParamsDto, + ) { + return this.service.findMyPrivateArchitectures({ + ...queryParams, + userId, + }); } @Get('public-architectures') - getMyPublicArchitectures(@Query() queryParams: QueryParamsDto) { - return this.service.findMyPublicArchitectures(queryParams); + @UseGuards(JwtAuthGuard) + getMyPublicArchitectures( + @User('id') userId: number, + @Query() queryParams: QueryParamsDto, + ) { + return this.service.findMyPublicArchitectures({ + ...queryParams, + userId, + }); } @Get('public-architectures/stars') - getMyStars(@Query() queryParams: QueryParamsDto) { - return this.service.findMyStars(queryParams); + @UseGuards(JwtAuthGuard) + getMyStars( + @User('id') userId: number, + @Query() queryParams: QueryParamsDto, + ) { + return this.service.findMyStars({ ...queryParams, userId }); } } diff --git a/apps/server/src/my/my.service.ts b/apps/server/src/my/my.service.ts index 1bc9097b..be3e7f21 100644 --- a/apps/server/src/my/my.service.ts +++ b/apps/server/src/my/my.service.ts @@ -1,45 +1,117 @@ import { Injectable } from '@nestjs/common'; import { PrismaService } from 'src/prisma/prisma.service'; -import { QueryParamsDto } from 'src/types/query-params.dto'; -import { buildQueryOptions } from 'src/utils/build-query-options'; +import { buildPaginationOptions } from 'src/utils/build-query-options'; +import { FindMyArchitecturesDto } from './dto/find-my-architectures.dto'; +import { buildFilterOptions } from 'src/utils/build-query-options'; +import { buildSortOptions } from 'src/utils/build-query-options'; @Injectable() export class MyService { constructor(private readonly prisma: PrismaService) {} - async findMyPrivateArchitectures(queryParams: QueryParamsDto) { - return this.prisma.privateArchitecture.findMany({ - ...buildQueryOptions(queryParams), - }); + async findMyPrivateArchitectures(queryParams: FindMyArchitecturesDto) { + const [data, total] = await this.prisma.$transaction([ + this.prisma.privateArchitecture.findMany({ + ...buildPaginationOptions(queryParams), + ...buildSortOptions(queryParams), + ...buildFilterOptions(queryParams), + }), + this.prisma.privateArchitecture.count({ + ...buildFilterOptions(queryParams), + }), + ]); + return { data, total }; } - async findMyPublicArchitectures(queryParams: QueryParamsDto) { - return this.prisma.publicArchitecture.findMany({ - include: { - author: true, - _count: { - select: { - stars: true, - imports: true, + async findMyPublicArchitectures(queryParams: FindMyArchitecturesDto) { + const [data, total] = await this.prisma.$transaction([ + this.prisma.publicArchitecture.findMany({ + include: { + author: { + select: { + id: true, + name: true, + }, + }, + tags: { + select: { + tag: { + select: { + name: true, + }, + }, + }, + }, + _count: { + select: { + stars: true, + imports: true, + }, }, }, - }, - ...buildQueryOptions(queryParams), - }); + ...buildPaginationOptions(queryParams), + ...buildSortOptions(queryParams), + ...buildFilterOptions(queryParams), + }), + this.prisma.publicArchitecture.count({ + ...buildFilterOptions(queryParams), + }), + ]); + return { data, total }; } - async findMyStars(queryParams: QueryParamsDto) { - return this.prisma.publicArchitecture.findMany({ - include: { - author: true, - _count: { - select: { - stars: true, - imports: true, + async findMyStars(queryParams: FindMyArchitecturesDto) { + const [data, total] = await this.prisma.$transaction([ + this.prisma.publicArchitecture.findMany({ + include: { + author: { + select: { + id: true, + name: true, + }, + }, + tags: { + select: { + tag: { + select: { + name: true, + }, + }, + }, + }, + _count: { + select: { + stars: true, + imports: true, + }, + }, + }, + ...buildPaginationOptions(queryParams), + ...buildSortOptions(queryParams), + where: { + stars: { + some: { + userId: queryParams.userId, + }, + }, + title: queryParams.search + ? { contains: queryParams.search } + : undefined, + }, + }), + this.prisma.publicArchitecture.count({ + where: { + stars: { + some: { + userId: queryParams.userId, + }, }, + title: queryParams.search + ? { contains: queryParams.search } + : undefined, }, - }, - ...buildQueryOptions(queryParams), - }); + }), + ]); + return { data, total }; } } From 239caa4c8f8a17d320d39fe137c8ec1ae152bdd0 Mon Sep 17 00:00:00 2001 From: paulcjy Date: Thu, 28 Nov 2024 12:46:10 +0900 Subject: [PATCH 009/124] =?UTF-8?q?=F0=9F=92=84=20Style(server):=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=EC=9E=90=20=ED=99=95=EC=9D=B8=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=EC=9D=84=20=EB=B3=84=EB=8F=84=EC=9D=98=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../private-architecture.service.ts | 79 +++++-------------- 1 file changed, 20 insertions(+), 59 deletions(-) diff --git a/apps/server/src/private-architecture/private-architecture.service.ts b/apps/server/src/private-architecture/private-architecture.service.ts index 883774b9..b9b44cb7 100644 --- a/apps/server/src/private-architecture/private-architecture.service.ts +++ b/apps/server/src/private-architecture/private-architecture.service.ts @@ -55,14 +55,8 @@ export class PrivateArchitectureService { architecture, cost, }: ModifyArchitectureDto) { - const privateArchitecture = - await this.prisma.privateArchitecture.findUnique({ - where: { - id, - authorId, - }, - }); - if (!privateArchitecture) throw new ForbiddenException(); + const isAuthor = await this.isArchitectureAuthor(id, authorId); + if (!isAuthor) throw new ForbiddenException(); return this.prisma.privateArchitecture.update({ where: { id, @@ -77,16 +71,9 @@ export class PrivateArchitectureService { } async removeArchitecture({ id, userId: authorId }: RemoveArchitectureDto) { + const isAuthor = await this.isArchitectureAuthor(id, authorId); + if (!isAuthor) throw new ForbiddenException(); return await this.prisma.$transaction(async (tx) => { - const privateArchitecture = await tx.privateArchitecture.findUnique( - { - where: { - id, - authorId, - }, - }, - ); - if (!privateArchitecture) throw new ForbiddenException(); await tx.version.deleteMany({ where: { privateArchitectureId: id, @@ -102,14 +89,8 @@ export class PrivateArchitectureService { } async findVersions({ id, userId: authorId }: FindVersionsDto) { - const privateArchitecture = - await this.prisma.privateArchitecture.findUnique({ - where: { - id, - authorId, - }, - }); - if (!privateArchitecture) throw new ForbiddenException(); + const isAuthor = await this.isArchitectureAuthor(id, authorId); + if (!isAuthor) throw new ForbiddenException(); return await this.prisma.version.findMany({ select: { id: true, @@ -129,20 +110,11 @@ export class PrivateArchitectureService { architecture, cost, }: SaveVersionDto) { - const privateArchitecture = - await this.prisma.privateArchitecture.findUnique({ - select: { - id: true, - }, - where: { - id, - authorId, - }, - }); - if (!privateArchitecture) throw new ForbiddenException(); + const isAuthor = await this.isArchitectureAuthor(id, authorId); + if (!isAuthor) throw new ForbiddenException(); return this.prisma.version.create({ data: { - privateArchitectureId: privateArchitecture.id, + privateArchitectureId: id, title, architecture, cost, @@ -151,17 +123,8 @@ export class PrivateArchitectureService { } async removeVersion({ userId: authorId, id, versionId }: RemoveVersionDto) { - const privateArchitecture = - await this.prisma.privateArchitecture.findUnique({ - select: { - id: true, - }, - where: { - id, - authorId, - }, - }); - if (!privateArchitecture) throw new ForbiddenException(); + const isAuthor = await this.isArchitectureAuthor(id, authorId); + if (!isAuthor) throw new ForbiddenException(); const privateArchitectureVersion = await this.prisma.version.findUnique( { where: { @@ -178,17 +141,8 @@ export class PrivateArchitectureService { } async findVersion({ userId: authorId, id, versionId }: FindVersionDto) { - const privateArchitecture = - await this.prisma.privateArchitecture.findUnique({ - select: { - id: true, - }, - where: { - id, - authorId, - }, - }); - if (!privateArchitecture) throw new ForbiddenException(); + const isAuthor = await this.isArchitectureAuthor(id, authorId); + if (!isAuthor) throw new ForbiddenException(); const privateArchitectureVersion = await this.prisma.version.findUnique( { select: { @@ -202,4 +156,11 @@ export class PrivateArchitectureService { if (!privateArchitectureVersion) throw new ForbiddenException(); return privateArchitectureVersion; } + + async isArchitectureAuthor(id: number, authorId: number): Promise { + return !!(await this.prisma.privateArchitecture.findUnique({ + select: { id: true }, + where: { id, authorId }, + })); + } } From a768bbff3d348853c295299fb8ff7a4ea0060714 Mon Sep 17 00:00:00 2001 From: paulcjy Date: Thu, 28 Nov 2024 12:47:27 +0900 Subject: [PATCH 010/124] =?UTF-8?q?=F0=9F=A4=96=20Refactor(server):=20publ?= =?UTF-8?q?ic=20=EB=AA=A8=EB=93=88=20=EC=99=84=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/find-architecture.dto.ts | 4 + .../dto/find-architectures.dto.ts | 7 + .../src/public-architecture/dto/import.dto.ts | 4 + .../dto/modify-architecture.dto.ts | 5 + .../dto/remove-architecture.dto.ts | 4 + .../dto/save-architecture.dto.ts | 7 + .../src/public-architecture/dto/star.dto.ts | 4 + .../src/public-architecture/dto/unstar.dto.ts | 4 + .../public-architecture.controller.ts | 81 ++++-- .../public-architecture.service.ts | 243 +++++++++++------- .../test/public-architecture.service.spec.ts | 20 +- apps/server/src/utils/build-query-options.ts | 53 ++-- 12 files changed, 296 insertions(+), 140 deletions(-) create mode 100644 apps/server/src/public-architecture/dto/find-architecture.dto.ts create mode 100644 apps/server/src/public-architecture/dto/find-architectures.dto.ts create mode 100644 apps/server/src/public-architecture/dto/import.dto.ts create mode 100644 apps/server/src/public-architecture/dto/modify-architecture.dto.ts create mode 100644 apps/server/src/public-architecture/dto/remove-architecture.dto.ts create mode 100644 apps/server/src/public-architecture/dto/save-architecture.dto.ts create mode 100644 apps/server/src/public-architecture/dto/star.dto.ts create mode 100644 apps/server/src/public-architecture/dto/unstar.dto.ts diff --git a/apps/server/src/public-architecture/dto/find-architecture.dto.ts b/apps/server/src/public-architecture/dto/find-architecture.dto.ts new file mode 100644 index 00000000..ea7ef58f --- /dev/null +++ b/apps/server/src/public-architecture/dto/find-architecture.dto.ts @@ -0,0 +1,4 @@ +export interface FindArchitectureDto { + id: number; + userId?: number; +} diff --git a/apps/server/src/public-architecture/dto/find-architectures.dto.ts b/apps/server/src/public-architecture/dto/find-architectures.dto.ts new file mode 100644 index 00000000..1dd83366 --- /dev/null +++ b/apps/server/src/public-architecture/dto/find-architectures.dto.ts @@ -0,0 +1,7 @@ +export interface FindArchitecturesDto { + page?: number; + limit?: number; + search?: string; + sort?: string; + order?: string; +} diff --git a/apps/server/src/public-architecture/dto/import.dto.ts b/apps/server/src/public-architecture/dto/import.dto.ts new file mode 100644 index 00000000..500a65dd --- /dev/null +++ b/apps/server/src/public-architecture/dto/import.dto.ts @@ -0,0 +1,4 @@ +export interface ImportDto { + id: number; + userId: number; +} diff --git a/apps/server/src/public-architecture/dto/modify-architecture.dto.ts b/apps/server/src/public-architecture/dto/modify-architecture.dto.ts new file mode 100644 index 00000000..1caa7415 --- /dev/null +++ b/apps/server/src/public-architecture/dto/modify-architecture.dto.ts @@ -0,0 +1,5 @@ +export interface ModifyArchitectureDto { + id: number; + userId: number; + title: string; +} diff --git a/apps/server/src/public-architecture/dto/remove-architecture.dto.ts b/apps/server/src/public-architecture/dto/remove-architecture.dto.ts new file mode 100644 index 00000000..3e77e893 --- /dev/null +++ b/apps/server/src/public-architecture/dto/remove-architecture.dto.ts @@ -0,0 +1,4 @@ +export interface RemoveArchitectureDto { + id: number; + userId: number; +} diff --git a/apps/server/src/public-architecture/dto/save-architecture.dto.ts b/apps/server/src/public-architecture/dto/save-architecture.dto.ts new file mode 100644 index 00000000..8eb2cab6 --- /dev/null +++ b/apps/server/src/public-architecture/dto/save-architecture.dto.ts @@ -0,0 +1,7 @@ +export interface SaveArchitectureDto { + title: string; + architecture: Record; + cost: number; + tags?: string[]; + userId: number; +} diff --git a/apps/server/src/public-architecture/dto/star.dto.ts b/apps/server/src/public-architecture/dto/star.dto.ts new file mode 100644 index 00000000..dda4977a --- /dev/null +++ b/apps/server/src/public-architecture/dto/star.dto.ts @@ -0,0 +1,4 @@ +export interface StarDto { + id: number; + userId: number; +} diff --git a/apps/server/src/public-architecture/dto/unstar.dto.ts b/apps/server/src/public-architecture/dto/unstar.dto.ts new file mode 100644 index 00000000..ebd0245d --- /dev/null +++ b/apps/server/src/public-architecture/dto/unstar.dto.ts @@ -0,0 +1,4 @@ +export interface UnstarDto { + id: number; + userId: number; +} diff --git a/apps/server/src/public-architecture/public-architecture.controller.ts b/apps/server/src/public-architecture/public-architecture.controller.ts index e733405f..e7ed70cc 100644 --- a/apps/server/src/public-architecture/public-architecture.controller.ts +++ b/apps/server/src/public-architecture/public-architecture.controller.ts @@ -15,63 +15,98 @@ import { CreatePublicArchitectureDto } from './dto/create-public-architecture.dt import { UpdatePublicArchitectureDto } from './dto/update-public-architecture.dto'; import { QueryParamsDto } from 'src/types/query-params.dto'; import { JwtAuthGuard } from 'src/guards/jwt-auth.guard'; +import { User } from 'src/decorators/user.decorator'; +import { OptionalAuthGuard } from 'src/guards/optional-auth.guard'; @Controller('public-architectures') export class PublicArchitectureController { constructor(private readonly service: PublicArchitectureService) {} @Get() - getMany(@Query() query: QueryParamsDto) { - return this.service.getMany(query); + getPublicArchitectures(@Query() queryParams: QueryParamsDto) { + return this.service.findArchitectures(queryParams); } @Post() @UseGuards(JwtAuthGuard) - create(@Body() createPublicDto: CreatePublicArchitectureDto) { - const userId = 1; - return this.service.create(userId, createPublicDto); + createPublicArchitecture( + @User('id') userId: number, + @Body() createPublicArchitectureDto: CreatePublicArchitectureDto, + ) { + return this.service.saveArchitecture({ + userId, + ...createPublicArchitectureDto, + }); } @Get(':id') - getOne(@Param('id', ParseIntPipe) id: number) { - return this.service.getOne(id); + @UseGuards(OptionalAuthGuard) + getPublicArchitecture( + @User('id') userId: number, + @Param('id', ParseIntPipe) id: number, + ) { + return this.service.findArchitecture({ id, userId }); } @Patch(':id') @UseGuards(JwtAuthGuard) - update( + updatePublicArchitecture( + @User('id') userId: number, @Param('id', ParseIntPipe) id: number, - @Body() updatePublicDto: UpdatePublicArchitectureDto, + @Body() updatePublicArchitectureDto: UpdatePublicArchitectureDto, ) { - const userId = 1; - return this.service.update(id, userId, updatePublicDto); + return this.service.modifyArchitecture({ + id, + userId, + ...updatePublicArchitectureDto, + }); } @Delete(':id') @UseGuards(JwtAuthGuard) - delete(@Param('id', ParseIntPipe) id: number) { - const userId = 1; - return this.service.delete(id, userId); + deletePublicArchitecture( + @User('id') userId: number, + @Param('id', ParseIntPipe) id: number, + ) { + return this.service.removeArchitecture({ id, userId }); } + // @Get(':id/stars') + // @UseGuards(OptionalAuthGuard) + // async getStars( + // @User('id') userId: number, + // @Param('id', ParseIntPipe) id: number, + // ) { + // if (!userId) return { isStarred: false }; + // const star = await this.service.findStar({ id, userId }); + // if (!star) return { isStarred: false }; + // return { isStarred: true }; + // } + @Post(':id/stars') @UseGuards(JwtAuthGuard) - star(@Param('id', ParseIntPipe) id: number) { - const userId = 1; - return this.service.star(id, userId); + starPublicArchitecture( + @User('id') userId: number, + @Param('id', ParseIntPipe) id: number, + ) { + return this.service.star({ id, userId }); } @Delete(':id/stars') @UseGuards(JwtAuthGuard) - unstar(@Param('id', ParseIntPipe) id: number) { - const userId = 1; - return this.service.unstar(id, userId); + unstarPublicArchitecture( + @User('id') userId: number, + @Param('id', ParseIntPipe) id: number, + ) { + return this.service.unstar({ id, userId }); } @Post(':id/imports') @UseGuards(JwtAuthGuard) - import(@Param('id', ParseIntPipe) id: number) { - const userId = 1; - return this.service.import(id, userId); + importPublicArchitecture( + @User('id') userId: number, + @Param('id', ParseIntPipe) id: number, + ) { + return this.service.import({ id, userId }); } } diff --git a/apps/server/src/public-architecture/public-architecture.service.ts b/apps/server/src/public-architecture/public-architecture.service.ts index 2ee18959..74d7a44b 100644 --- a/apps/server/src/public-architecture/public-architecture.service.ts +++ b/apps/server/src/public-architecture/public-architecture.service.ts @@ -1,62 +1,113 @@ -import { Injectable, NotFoundException } from '@nestjs/common'; -import { CreatePublicArchitectureDto } from './dto/create-public-architecture.dto'; -import { UpdatePublicArchitectureDto } from './dto/update-public-architecture.dto'; +import { Injectable } from '@nestjs/common'; import { PrismaService } from 'src/prisma/prisma.service'; -import { QueryParamsDto } from 'src/types/query-params.dto'; +import { + buildFilterOptions, + buildPaginationOptions, + buildSortOptions, +} from 'src/utils/build-query-options'; +import { FindArchitecturesDto } from './dto/find-architectures.dto'; +import { SaveArchitectureDto } from './dto/save-architecture.dto'; +import { FindArchitectureDto } from './dto/find-architecture.dto'; +import { ModifyArchitectureDto } from './dto/modify-architecture.dto'; +import { RemoveArchitectureDto } from './dto/remove-architecture.dto'; +import { UnstarDto } from './dto/unstar.dto'; +import { StarDto } from './dto/star.dto'; +import { ImportDto } from './dto/import.dto'; @Injectable() export class PublicArchitectureService { constructor(private readonly prisma: PrismaService) {} - async getMany(query: QueryParamsDto) { - const { page, limit, search, sort, order } = query; - - return this.prisma.publicArchitecture.findMany({ - include: { - author: true, - _count: { - select: { - stars: true, - imports: true, + async findArchitectures(queryParams: FindArchitecturesDto) { + const [data, total] = await this.prisma.$transaction([ + this.prisma.publicArchitecture.findMany({ + include: { + author: { + select: { + id: true, + name: true, + }, + }, + tags: { + select: { + tag: { + select: { + name: true, + }, + }, + }, + }, + _count: { + select: { + stars: true, + imports: true, + }, }, }, - }, - skip: (page - 1) * limit, - take: limit, - where: search - ? { - title: { - contains: search, - }, - } - : undefined, - orderBy: sort - ? { - [sort]: order, - } - : { - createdAt: 'desc', - }, - }); + ...buildPaginationOptions(queryParams), + ...buildSortOptions(queryParams), + ...buildFilterOptions(queryParams), + }), + this.prisma.publicArchitecture.count({ + ...buildFilterOptions(queryParams), + }), + ]); + return { data, total }; } - async create(userId: number, dto: CreatePublicArchitectureDto) { - return await this.prisma.publicArchitecture.create({ + saveArchitecture({ + title, + architecture, + cost, + tags, + userId: authorId, + }: SaveArchitectureDto) { + return this.prisma.publicArchitecture.create({ data: { - ...dto, - authorId: userId, - }, - include: { - author: true, + title, + architecture, + cost, + authorId, + tags: { + create: tags.map((name) => ({ + tag: { + connectOrCreate: { + where: { name }, + create: { name }, + }, + }, + })), + }, }, }); } - async getOne(id: number) { - const item = await this.prisma.publicArchitecture.findUnique({ + findArchitecture({ id, userId }: FindArchitectureDto) { + return this.prisma.publicArchitecture.findUniqueOrThrow({ where: { id }, include: { - author: true, + author: { + select: { + id: true, + name: true, + }, + }, + tags: { + select: { + tag: { + select: { + name: true, + }, + }, + }, + }, + stars: userId + ? { + where: { + userId, + }, + } + : undefined, _count: { select: { stars: true, @@ -65,81 +116,89 @@ export class PublicArchitectureService { }, }, }); - - if (!item) { - throw new NotFoundException('Architecture not found'); - } - - return item; } - async update(id: number, userId: number, dto: UpdatePublicArchitectureDto) { - // #TODO check if user is the author + modifyArchitecture({ id, userId: authorId, title }: ModifyArchitectureDto) { return this.prisma.publicArchitecture.update({ - where: { id }, - data: dto, - include: { - author: true, - }, + where: { id, authorId }, + data: { title }, }); } - async delete(id: number, userId: number) { - // #TODO check if user is the author - // #TODO delete all stars and imports - return this.prisma.publicArchitecture.delete({ where: { id } }); + async removeArchitecture({ id, userId: authorId }: RemoveArchitectureDto) { + return this.prisma.$transaction(async (tx) => { + await tx.publicArchitecture.findUniqueOrThrow({ + select: { id: true }, + where: { id, authorId }, + }); + await tx.publicArchitectureTag.deleteMany({ + where: { publicArchitectureId: id }, + }); + await tx.star.deleteMany({ where: { publicArchitectureId: id } }); + await tx.import.deleteMany({ where: { publicArchitectureId: id } }); + return await tx.publicArchitecture.delete({ where: { id } }); + }); } - async star(id: number, userId: number) { - const exists = await this.architectureExists(id); - - if (!exists) { - throw new NotFoundException('Architecture not found'); - } + findStar({ id, userId }: StarDto) { + return this.prisma.star.findUnique({ + where: { + unique_star: { + publicArchitectureId: id, + userId, + }, + }, + }); + } + star({ id, userId }: StarDto) { return this.prisma.star.create({ data: { - userId, publicArchitectureId: id, + userId, }, }); } - async unstar(id: number, userId: number) { - const exists = await this.architectureExists(id); - - if (!exists) { - throw new NotFoundException('Architecture not found'); - } - + unstar({ id, userId }: UnstarDto) { return this.prisma.star.delete({ where: { unique_star: { - userId, publicArchitectureId: id, + userId, }, }, }); } - async import(id: number, userId: number) { - const exists = await this.architectureExists(id); - - if (!exists) { - throw new NotFoundException('Architecture not found'); - } - - return this.prisma.import.create({ - data: { - userId, - publicArchitectureId: id, - }, - }); - } + async import({ id, userId }: ImportDto) { + const { title, architecture, cost } = + await this.prisma.publicArchitecture.findUniqueOrThrow({ + select: { + title: true, + architecture: true, + cost: true, + }, + where: { id }, + }); + + const [privateArchitecture] = await this.prisma.$transaction([ + this.prisma.privateArchitecture.create({ + data: { + title, + architecture, + cost, + authorId: userId, + }, + }), + this.prisma.import.create({ + data: { + publicArchitectureId: id, + userId, + }, + }), + ]); - architectureExists(id: number) { - return this.prisma.publicArchitecture.findUnique({ - where: { id }, - }); + return privateArchitecture; } } diff --git a/apps/server/src/public-architecture/test/public-architecture.service.spec.ts b/apps/server/src/public-architecture/test/public-architecture.service.spec.ts index a3610bae..7bc23247 100644 --- a/apps/server/src/public-architecture/test/public-architecture.service.spec.ts +++ b/apps/server/src/public-architecture/test/public-architecture.service.spec.ts @@ -55,7 +55,7 @@ describe('PublicService', () => { it('should return all architectures', async () => { repository.findAll.mockReturnValue([mockArchitecture]); - const result = await service.getMany(); + const result = await service.findArchitectures(); expect(result).toEqual([mockArchitecture]); expect(repository.findAll).toHaveBeenCalled(); @@ -66,7 +66,7 @@ describe('PublicService', () => { it('should return an architecture by id', async () => { repository.findById.mockReturnValue(mockArchitecture); - const result = await service.getOne(1); + const result = await service.findArchitecture(1); expect(result).toEqual(mockArchitecture); expect(repository.findById).toHaveBeenCalledWith(1); @@ -75,7 +75,9 @@ describe('PublicService', () => { it('should throw NotFoundException when not found', async () => { repository.findById.mockReturnValue(null); - await expect(service.getOne(1)).rejects.toThrow(NotFoundException); + await expect(service.findArchitecture(1)).rejects.toThrow( + NotFoundException, + ); }); }); @@ -90,7 +92,7 @@ describe('PublicService', () => { it('should create an architecture', async () => { repository.create.mockReturnValue(mockArchitecture); - const result = await service.create(1, createDto); + const result = await service.saveArchitecture(1, createDto); expect(result).toEqual(mockArchitecture); expect(repository.create).toHaveBeenCalledWith(1, createDto); @@ -107,7 +109,7 @@ describe('PublicService', () => { ...updateDto, }); - const result = await service.update(1, updateDto); + const result = await service.modifyArchitecture(1, updateDto); expect(result.title).toBe('Updated'); expect(repository.update).toHaveBeenCalledWith(1, updateDto); @@ -116,9 +118,9 @@ describe('PublicService', () => { it('should throw NotFoundException when not found', async () => { repository.findById.mockReturnValue(null); - await expect(service.update(1, updateDto)).rejects.toThrow( - NotFoundException, - ); + await expect( + service.modifyArchitecture(1, updateDto), + ).rejects.toThrow(NotFoundException); }); }); @@ -127,7 +129,7 @@ describe('PublicService', () => { repository.findById.mockReturnValue(mockArchitecture); repository.delete.mockReturnValue(mockArchitecture); - const result = await service.delete(1); + const result = await service.removeArchitecture(1); expect(result).toEqual(mockArchitecture); expect(repository.delete).toHaveBeenCalledWith(1); diff --git a/apps/server/src/utils/build-query-options.ts b/apps/server/src/utils/build-query-options.ts index 3fb79ca1..48707a64 100644 --- a/apps/server/src/utils/build-query-options.ts +++ b/apps/server/src/utils/build-query-options.ts @@ -1,16 +1,44 @@ import { QueryParamsDto } from 'src/types/query-params.dto'; -export const buildQueryOptions = ({ - page, - limit, - search, - sort, - order, - userId, -}: QueryParamsDto & { userId?: number }) => { +export const buildPaginationOptions = ({ page, limit }: QueryParamsDto) => { return { skip: (page - 1) * limit, take: limit, + } as any; +}; + +export const buildSortOptions = ({ sort, order }: QueryParamsDto) => { + if (sort === 'name') { + return { + orderBy: { + title: order, + }, + }; + } else if (sort === 'cost') { + return { + orderBy: { + cost: order, + }, + }; + } else if (sort === 'stars' || sort === 'imports') { + return { + orderBy: { + [sort]: { + _count: order, + }, + }, + }; + } +}; + +export const buildFilterOptions = ({ + search, + userId, +}: { + search?: string; + userId?: number; +}) => { + return { where: search || userId ? { @@ -20,12 +48,5 @@ export const buildQueryOptions = ({ authorId: userId, } : undefined, - orderBy: sort - ? { - [sort]: order, - } - : { - createdAt: 'desc', - }, - } as any; + }; }; From ea68d20eb8c2f982dedf5b07129742324f12607d Mon Sep 17 00:00:00 2001 From: Geonhyuk Seo Date: Thu, 28 Nov 2024 12:51:38 +0900 Subject: [PATCH 011/124] =?UTF-8?q?=F0=9F=9A=9A=20Setting(server):=20?= =?UTF-8?q?=EC=84=9C=EB=B2=84=20=EC=AA=BD=20=EB=8F=84=EC=BB=A4=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=EC=A7=80=20=EB=B0=8F=20=EB=8F=84=EC=BB=A4=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EC=A6=88=EC=97=90=20NODE=5FENV=20=ED=99=98=EA=B2=BD?= =?UTF-8?q?=20=EB=B3=80=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/server/Dockerfile | 1 + docker-composes/cloud-canvas-back.yml | 1 + packages/ncloud-sdk/package.json | 9 ++-- pnpm-lock.yaml | 59 +++++++++++++++++++++++---- 4 files changed, 55 insertions(+), 15 deletions(-) diff --git a/apps/server/Dockerfile b/apps/server/Dockerfile index 9f2c8c35..9227f94a 100644 --- a/apps/server/Dockerfile +++ b/apps/server/Dockerfile @@ -23,5 +23,6 @@ COPY ./apps/server/prisma/ /app/prisma/ ENV DATABASE_URL=mysql://johndoe:randompassword@localhost:3306/mydb ENV PORT=3000 +ENV NODE_ENV=development EXPOSE 3000 ENTRYPOINT ["sh", "-c", "npx prisma generate && node ./dist/src/main.js"] \ No newline at end of file diff --git a/docker-composes/cloud-canvas-back.yml b/docker-composes/cloud-canvas-back.yml index 5d7148ea..c1021da0 100644 --- a/docker-composes/cloud-canvas-back.yml +++ b/docker-composes/cloud-canvas-back.yml @@ -3,6 +3,7 @@ services: image: t84ar7xr.kr.private-ncr.ntruss.com/back:dev container_name: back environment: + NODE_ENV: ${NODE_ENV} DATABASE_URL: ${DATABASE_URL} REDIS_HOST: ${REDIS_HOST} REDIS_PORT: ${REDIS_PORT} diff --git a/packages/ncloud-sdk/package.json b/packages/ncloud-sdk/package.json index a8723f8f..f658f4b0 100644 --- a/packages/ncloud-sdk/package.json +++ b/packages/ncloud-sdk/package.json @@ -4,12 +4,9 @@ "description": "", "private": true, "keywords": [], - "exports": { - ".": { - "types": "./src/index.ts", - "default": "./dist/index.js" - } - }, + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "type": "commonjs", "scripts": { "start": "cross-env NODE_ENV=development node dist/index.js", "dev": "tsx watch src/index.ts", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ce51f364..62849c8f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -151,6 +151,9 @@ importers: '@nestjs/platform-express': specifier: ^10.0.0 version: 10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7) + '@nestjs/schedule': + specifier: ^4.1.1 + version: 4.1.1(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7) '@nestjs/swagger': specifier: ^8.0.5 version: 8.0.5(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2) @@ -1608,6 +1611,12 @@ packages: '@nestjs/common': ^10.0.0 '@nestjs/core': ^10.0.0 + '@nestjs/schedule@4.1.1': + resolution: {integrity: sha512-VxAnCiU4HP0wWw8IdWAVfsGC/FGjyToNjjUtXDEQL6oj+w/N5QDd2VT9k6d7Jbr8PlZuBZNdWtDKSkH5bZ+RXQ==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 + '@nestjs/core': ^8.0.0 || ^9.0.0 || ^10.0.0 + '@nestjs/schematics@10.2.3': resolution: {integrity: sha512-4e8gxaCk7DhBxVUly2PjYL4xC2ifDFexCqq1/u4TtivLGXotVk0wHdYuPYe1tHTHuR1lsOkRbfOCpkdTnigLVg==} peerDependencies: @@ -2080,6 +2089,9 @@ packages: '@types/jsonwebtoken@9.0.5': resolution: {integrity: sha512-VRLSGzik+Unrup6BsouBeHsf4d1hOEgYWTm/7Nmw1sXoN1+tRly/Gy/po3yeahnP4jfnQWWAhQAqcNfH7ngOkA==} + '@types/luxon@3.4.2': + resolution: {integrity: sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==} + '@types/methods@1.1.4': resolution: {integrity: sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==} @@ -2947,6 +2959,9 @@ packages: create-require@1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + cron@3.1.7: + resolution: {integrity: sha512-tlBg7ARsAMQLzgwqVxy8AZl/qlTc5nibqYwtNGoCrd+cV+ugI+tvZC1oT/8dFH8W455YrywGykx/KMmAqOr7Jw==} + cross-env@7.0.3: resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==} engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'} @@ -4462,6 +4477,10 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + luxon@3.4.4: + resolution: {integrity: sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==} + engines: {node: '>=12'} + magic-string@0.30.12: resolution: {integrity: sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==} @@ -5940,6 +5959,10 @@ packages: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} + uuid@10.0.0: + resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} + hasBin: true + v8-compile-cache-lib@3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} @@ -7453,6 +7476,13 @@ snapshots: transitivePeerDependencies: - supports-color + '@nestjs/schedule@4.1.1(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)': + dependencies: + '@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/core': 10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1) + cron: 3.1.7 + uuid: 10.0.0 + '@nestjs/schematics@10.2.3(chokidar@3.6.0)(typescript@5.6.3)': dependencies: '@angular-devkit/core': 17.3.11(chokidar@3.6.0) @@ -7824,6 +7854,8 @@ snapshots: dependencies: '@types/node': 22.9.0 + '@types/luxon@3.4.2': {} + '@types/methods@1.1.4': {} '@types/mime@1.3.5': {} @@ -8852,6 +8884,11 @@ snapshots: create-require@1.1.1: {} + cron@3.1.7: + dependencies: + '@types/luxon': 3.4.2 + luxon: 3.4.4 + cross-env@7.0.3: dependencies: cross-spawn: 7.0.5 @@ -9259,8 +9296,8 @@ snapshots: '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.6.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1) eslint-plugin-react: 7.37.2(eslint@8.57.1) eslint-plugin-react-hooks: 5.0.0(eslint@8.57.1) @@ -9283,37 +9320,37 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1): + eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.3.7 enhanced-resolve: 5.17.1 eslint: 8.57.1 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) fast-glob: 3.3.2 get-tsconfig: 4.8.1 is-bun-module: 1.2.1 is-glob: 4.0.3 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) transitivePeerDependencies: - '@typescript-eslint/parser' - eslint-import-resolver-node - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): + eslint-module-utils@2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.6.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -9324,7 +9361,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -10825,6 +10862,8 @@ snapshots: dependencies: yallist: 3.1.1 + luxon@3.4.4: {} + magic-string@0.30.12: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 @@ -12367,6 +12406,8 @@ snapshots: utils-merge@1.0.1: {} + uuid@10.0.0: {} + v8-compile-cache-lib@3.0.1: {} v8-to-istanbul@9.3.0: From 320e28c285381f670b69f29d2644c2e38eab9374 Mon Sep 17 00:00:00 2001 From: paulcjy Date: Thu, 28 Nov 2024 12:54:42 +0900 Subject: [PATCH 012/124] =?UTF-8?q?=F0=9F=A4=96=20Refactor(server):=20?= =?UTF-8?q?=EC=9E=84=EC=8B=9C=EB=A1=9C=20private=20=EB=AA=A8=EB=93=88=20?= =?UTF-8?q?=EB=B9=84=ED=99=9C=EC=84=B1=ED=99=94=ED=96=88=EB=8D=98=20?= =?UTF-8?q?=EA=B2=83=20=EB=8B=A4=EC=8B=9C=20=ED=99=9C=EC=84=B1=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/server/src/app.module.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/server/src/app.module.ts b/apps/server/src/app.module.ts index c235e849..866aec95 100644 --- a/apps/server/src/app.module.ts +++ b/apps/server/src/app.module.ts @@ -5,9 +5,8 @@ import { AppService } from './app.service'; import { AuthModule } from 'src/auth/auth.module'; import { UserModule } from 'src/user/user.module'; import { PublicArchitectureModule } from 'src/public-architecture/public-architecture.module'; -// import { PrivateArchitectureModule } from 'src/private-architecture/private-architecture.module'; +import { PrivateArchitectureModule } from 'src/private-architecture/private-architecture.module'; import { PrismaService } from 'src/prisma/prisma.service'; -// import { routes } from './routes'; import { MyModule } from './my/my.module'; @Module({ @@ -16,7 +15,7 @@ import { MyModule } from './my/my.module'; AuthModule, UserModule, PublicArchitectureModule, - // PrivateArchitectureModule, + PrivateArchitectureModule, MyModule, ], controllers: [AppController], From 3f5f56bdbbd2d5d6dd8546204a791484e8c24dd5 Mon Sep 17 00:00:00 2001 From: Geonhyuk Seo Date: Thu, 28 Nov 2024 12:54:44 +0900 Subject: [PATCH 013/124] =?UTF-8?q?=F0=9F=9A=9A=20Setting(docker-composes)?= =?UTF-8?q?:=20=EC=84=9C=EB=B2=84=20=EB=8F=84=EC=BB=A4=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EC=A6=88=20=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8?= =?UTF-8?q?=EC=97=90=20NODE=5FENV=20=EA=B0=92=EC=9D=84=20=EC=83=81?= =?UTF-8?q?=EC=88=98=20production=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-composes/cloud-canvas-back.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-composes/cloud-canvas-back.yml b/docker-composes/cloud-canvas-back.yml index c1021da0..818cf247 100644 --- a/docker-composes/cloud-canvas-back.yml +++ b/docker-composes/cloud-canvas-back.yml @@ -3,7 +3,7 @@ services: image: t84ar7xr.kr.private-ncr.ntruss.com/back:dev container_name: back environment: - NODE_ENV: ${NODE_ENV} + NODE_ENV: production DATABASE_URL: ${DATABASE_URL} REDIS_HOST: ${REDIS_HOST} REDIS_PORT: ${REDIS_PORT} From 139a8a95b5cfaffdc85d50c1feed334331f2a0dd Mon Sep 17 00:00:00 2001 From: paulcjy Date: Thu, 28 Nov 2024 12:55:45 +0900 Subject: [PATCH 014/124] =?UTF-8?q?=F0=9F=9A=9A=20Setting(server):=20?= =?UTF-8?q?=EA=B0=9C=EB=B0=9C=20=ED=99=98=EA=B2=BD=EC=97=90=EC=84=9C=20ena?= =?UTF-8?q?ble=20cors=20=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/server/src/main.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/apps/server/src/main.ts b/apps/server/src/main.ts index 5a0260e3..f661715c 100644 --- a/apps/server/src/main.ts +++ b/apps/server/src/main.ts @@ -5,6 +5,7 @@ import { ValidationPipe } from '@nestjs/common'; import helmet from 'helmet'; import * as cookieParser from 'cookie-parser'; import { PrismaExceptionFilter } from './filters/prisma-exception.filter'; +import { ConfigService } from '@nestjs/config'; async function bootstrap() { const app = await NestFactory.create(AppModule); @@ -28,6 +29,25 @@ async function bootstrap() { const { httpAdapter } = app.get(HttpAdapterHost); app.useGlobalFilters(new PrismaExceptionFilter(httpAdapter)); + const configService = app.get(ConfigService); + const environment = configService.get('NODE_ENV'); + + if (environment !== 'production') { + app.enableCors({ + origin: 'http://localhost:3001', + methods: [ + 'GET', + 'HEAD', + 'PUT', + 'PATCH', + 'POST', + 'DELETE', + 'OPTIONS', + ], + allowedHeaders: ['Content-Type', 'Authorization', 'Accept'], + credentials: true, + }); + } await app.listen(3000); } bootstrap(); From e9d032e69a0ba887a4ed0451ccaf7bbd881e0197 Mon Sep 17 00:00:00 2001 From: paulcjy Date: Thu, 28 Nov 2024 12:56:42 +0900 Subject: [PATCH 015/124] =?UTF-8?q?=F0=9F=90=9B=20Design(hub):=20ui=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/hub/src/ui/Button.tsx | 2 +- apps/hub/src/ui/ErrorMessage.tsx | 11 ++++------- apps/hub/src/ui/ImportIcon.tsx | 10 ++++++++++ apps/hub/src/ui/LinkButton.tsx | 2 +- apps/hub/src/ui/StarIcon.tsx | 10 ++++++++++ apps/hub/src/ui/Tag.tsx | 2 +- 6 files changed, 27 insertions(+), 10 deletions(-) create mode 100644 apps/hub/src/ui/ImportIcon.tsx create mode 100644 apps/hub/src/ui/StarIcon.tsx diff --git a/apps/hub/src/ui/Button.tsx b/apps/hub/src/ui/Button.tsx index f2fb9a64..3c576cf1 100644 --- a/apps/hub/src/ui/Button.tsx +++ b/apps/hub/src/ui/Button.tsx @@ -9,7 +9,7 @@ export const Button = ({ }) => { return ( +

Error: {message}

+ ); diff --git a/apps/hub/src/ui/ImportIcon.tsx b/apps/hub/src/ui/ImportIcon.tsx new file mode 100644 index 00000000..a863de8a --- /dev/null +++ b/apps/hub/src/ui/ImportIcon.tsx @@ -0,0 +1,10 @@ +export const ImportIcon = ({ size = 28 }: { size?: number }) => ( + + + +); diff --git a/apps/hub/src/ui/LinkButton.tsx b/apps/hub/src/ui/LinkButton.tsx index 09c6c19a..286d899f 100644 --- a/apps/hub/src/ui/LinkButton.tsx +++ b/apps/hub/src/ui/LinkButton.tsx @@ -4,7 +4,7 @@ export const LinkButton = ({ text, href }: { text: string; href: string }) => { return ( {text} diff --git a/apps/hub/src/ui/StarIcon.tsx b/apps/hub/src/ui/StarIcon.tsx new file mode 100644 index 00000000..ec1b948f --- /dev/null +++ b/apps/hub/src/ui/StarIcon.tsx @@ -0,0 +1,10 @@ +export const StarIcon = ({ size = 20 }: { size?: number }) => ( + + + +); diff --git a/apps/hub/src/ui/Tag.tsx b/apps/hub/src/ui/Tag.tsx index 23e4aaad..06715765 100644 --- a/apps/hub/src/ui/Tag.tsx +++ b/apps/hub/src/ui/Tag.tsx @@ -3,7 +3,7 @@ import Link from 'next/link'; export const Tag = ({ tag }: { tag: string }) => ( {tag} From 31b235b64ada2b069c6f868bdc709f5496af1629 Mon Sep 17 00:00:00 2001 From: paulcjy Date: Thu, 28 Nov 2024 12:57:20 +0900 Subject: [PATCH 016/124] =?UTF-8?q?=F0=9F=A4=96=20Refactor(hub):=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=9A=A9=20mock=20api=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/hub/src/api/architectures.api.ts | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 apps/hub/src/api/architectures.api.ts diff --git a/apps/hub/src/api/architectures.api.ts b/apps/hub/src/api/architectures.api.ts deleted file mode 100644 index f0404bb8..00000000 --- a/apps/hub/src/api/architectures.api.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { mockFetch } from '@/mocks/architectures'; - -export interface ArchitectureQueryParams { - page?: number; - search?: string; - sort?: string; - order?: string; -} - -export const fetchArchitectures = async (params: ArchitectureQueryParams) => { - const response = await mockFetch(params); - return response; -}; From b064f1e08363f8112a9ae6202270685ce2e54ca1 Mon Sep 17 00:00:00 2001 From: paulcjy Date: Thu, 28 Nov 2024 12:58:48 +0900 Subject: [PATCH 017/124] =?UTF-8?q?=F0=9F=9A=9A=20Setting(hub):=20next.js?= =?UTF-8?q?=20=EB=B9=8C=EB=93=9C=20=EC=98=B5=EC=85=98=EC=9D=84=20standalon?= =?UTF-8?q?e=EC=9C=BC=EB=A1=9C=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/hub/next.config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/hub/next.config.ts b/apps/hub/next.config.ts index bb668f40..fa3438ad 100644 --- a/apps/hub/next.config.ts +++ b/apps/hub/next.config.ts @@ -2,6 +2,7 @@ import type { NextConfig } from 'next'; const nextConfig: NextConfig = { /* config options here */ + output: 'standalone', }; export default nextConfig; From 837e746627e8378a0d562ad166cde2530d93a7bf Mon Sep 17 00:00:00 2001 From: paulcjy Date: Thu, 28 Nov 2024 12:59:23 +0900 Subject: [PATCH 018/124] =?UTF-8?q?=E2=9C=A8=20Feat(hub):=20global=20heade?= =?UTF-8?q?r=EC=97=90=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=EC=9D=84=20=EC=8B=A4=EC=A0=9C=20api=EB=A1=9C=20=EC=99=84?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/hub/src/components/GlobalHeader.tsx | 27 +++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/apps/hub/src/components/GlobalHeader.tsx b/apps/hub/src/components/GlobalHeader.tsx index ca779588..5b072e49 100644 --- a/apps/hub/src/components/GlobalHeader.tsx +++ b/apps/hub/src/components/GlobalHeader.tsx @@ -2,19 +2,40 @@ import Link from 'next/link'; import { useRouter } from 'next/navigation'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { LinkButton } from '../ui/LinkButton'; export const GlobalHeader = () => { const router = useRouter(); const [isLoggedIn, setIsLoggedIn] = useState(false); - const handleLogin = () => { - setIsLoggedIn(true); + useEffect(() => { + if (localStorage.getItem('isLoggedIn') === 'true') { + setIsLoggedIn(true); + } + }, []); + + const handleLogin = async () => { + const res = await fetch('http://localhost:3000/auth/login', { + method: 'POST', + credentials: 'include', + }); + if (res.ok) { + setIsLoggedIn(true); + localStorage.setItem('isLoggedIn', 'true'); + router.refresh(); + router.push('/'); + } }; const handleLogout = () => { + fetch('http://localhost:3000/auth/logout', { + method: 'POST', + credentials: 'include', + }); setIsLoggedIn(false); + localStorage.removeItem('isLoggedIn'); + router.refresh(); router.push('/'); }; From fd507744b52f0a775bd2c39e35532099b61b9705 Mon Sep 17 00:00:00 2001 From: paulcjy Date: Thu, 28 Nov 2024 13:00:26 +0900 Subject: [PATCH 019/124] =?UTF-8?q?=E2=9C=A8=20Feat(hub):=20architecture?= =?UTF-8?q?=20board=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ArchitectureBoard/ArchitectureItem.tsx | 24 ++++++++++--------- .../ArchitectureBoard/ArchitectureList.tsx | 6 +---- .../components/ArchitectureBoard/index.tsx | 2 ++ apps/hub/src/hooks/useQueryData.ts | 2 -- apps/hub/src/utils/fetcher.ts | 12 +++++----- 5 files changed, 22 insertions(+), 24 deletions(-) diff --git a/apps/hub/src/components/ArchitectureBoard/ArchitectureItem.tsx b/apps/hub/src/components/ArchitectureBoard/ArchitectureItem.tsx index 7700ed47..a77020bb 100644 --- a/apps/hub/src/components/ArchitectureBoard/ArchitectureItem.tsx +++ b/apps/hub/src/components/ArchitectureBoard/ArchitectureItem.tsx @@ -7,19 +7,21 @@ export const ArchitectureItem = ({ author, cost, createdAt, - stars, - imports, tags, + _count, }: { id: number; title: string; - author: string; + author: { id: number; name: string }; cost: number; createdAt: string; - stars: number; - imports: number; - tags: string[]; + tags: { tag: { name: string } }[]; + _count: { + imports: number; + stars: number; + }; }) => { + const { imports, stars } = _count; return (
  • @@ -27,12 +29,12 @@ export const ArchitectureItem = ({ {title}
    -
    {createdAt}
    -
    {author}
    +
    {new Date(createdAt).toLocaleString()}
    +
    {author.name}
    -
    - {tags.map((tag) => ( - +
    + {tags?.map(({ tag: { name } }) => ( + ))}
    diff --git a/apps/hub/src/components/ArchitectureBoard/ArchitectureList.tsx b/apps/hub/src/components/ArchitectureBoard/ArchitectureList.tsx index cc37f07d..c8bd053e 100644 --- a/apps/hub/src/components/ArchitectureBoard/ArchitectureList.tsx +++ b/apps/hub/src/components/ArchitectureBoard/ArchitectureList.tsx @@ -1,6 +1,6 @@ import { ArchitectureItem } from './ArchitectureItem'; -export const ArchitectureList = ({ data }) => { +export const ArchitectureList = ({ data }: { data: Array }) => { if (!data?.length) { return (
    @@ -13,13 +13,9 @@ export const ArchitectureList = ({ data }) => { return (
    - {/*
    */} - {/*
    */} {data.map((item) => ( ))} - {/*
    */} - {/*
    */}
    ); }; diff --git a/apps/hub/src/components/ArchitectureBoard/index.tsx b/apps/hub/src/components/ArchitectureBoard/index.tsx index 040d42fb..19900dbc 100644 --- a/apps/hub/src/components/ArchitectureBoard/index.tsx +++ b/apps/hub/src/components/ArchitectureBoard/index.tsx @@ -25,6 +25,8 @@ export const ArchitectureBoard = ({ apiUrl }: { apiUrl: string }) => { if (error) return ; + // return
    {JSON.stringify(data)}
    ; + return (
    diff --git a/apps/hub/src/hooks/useQueryData.ts b/apps/hub/src/hooks/useQueryData.ts index eebbc70c..68ec7d36 100644 --- a/apps/hub/src/hooks/useQueryData.ts +++ b/apps/hub/src/hooks/useQueryData.ts @@ -21,8 +21,6 @@ export const useQueryData = ( return `${apiUrl}?${searchParams.toString()}`; }; - console.log('buildUrl', buildUrl()); - const { data, error, isLoading, mutate } = useSWR(buildUrl, fetcher); return { diff --git a/apps/hub/src/utils/fetcher.ts b/apps/hub/src/utils/fetcher.ts index 01f6b73c..54758d5a 100644 --- a/apps/hub/src/utils/fetcher.ts +++ b/apps/hub/src/utils/fetcher.ts @@ -1,8 +1,8 @@ -import { mockFetch } from '@/mocks/architectures'; - export const fetcher = async (url: string) => { - const res = await mockFetch(url); - // if (!res.ok) throw new Error('Failed to fetch data'); - // return res.json(); - return res; + const res = await fetch(url, { + credentials: 'include', + }); + if (!res.ok) throw new Error('Failed to fetch data'); + const data = await res.json(); + return data; }; From 46a081281ee958aa79fe3e73c6e7c812000e4339 Mon Sep 17 00:00:00 2001 From: paulcjy Date: Thu, 28 Nov 2024 13:00:55 +0900 Subject: [PATCH 020/124] =?UTF-8?q?=E2=9C=A8=20Feat(hub):=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EA=B1=B0=EC=9D=98=20=EC=99=84=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/hub/src/app/architectures/[id]/page.tsx | 134 +++++++++++++------ apps/hub/src/app/my/architectures/page.tsx | 10 +- apps/hub/src/app/my/shared/page.tsx | 10 +- apps/hub/src/app/my/starred/page.tsx | 10 +- apps/hub/src/app/page.tsx | 7 +- 5 files changed, 122 insertions(+), 49 deletions(-) diff --git a/apps/hub/src/app/architectures/[id]/page.tsx b/apps/hub/src/app/architectures/[id]/page.tsx index 85651c12..0096959f 100644 --- a/apps/hub/src/app/architectures/[id]/page.tsx +++ b/apps/hub/src/app/architectures/[id]/page.tsx @@ -1,71 +1,121 @@ 'use client'; -import { Button } from '@/ui/Button'; +import { ErrorMessage } from '@/ui/ErrorMessage'; +import { ImportIcon } from '@/ui/ImportIcon'; +import { LoadingSpinner } from '@/ui/LoadingSpinner'; +import { StarIcon } from '@/ui/StarIcon'; +import { Tag } from '@/ui/Tag'; +import { fetcher } from '@/utils/fetcher'; import { useParams } from 'next/navigation'; -import { useEffect, useState } from 'react'; +import useSWR from 'swr'; interface PublicArchitecture { - id: string; + id: number; title: string; - author: string; - createdAt: Date; - architecture: string; - stars: number; - imports: number; + author: { id: number; name: string }; + createdAt: string; + architecture: Record; + cost: number; + tags: { tag: { name: string } }[]; + stars: any[]; + _count: { + stars: number; + imports: number; + }; } export default function ArchitectureDetailPage() { const params = useParams<{ id: string }>(); - const [architecture, setArchitecture] = useState( - {} as any, + const { data, error, isLoading, mutate } = useSWR( + `http://localhost:3000/public-architectures/${params.id}`, + fetcher, ); - useEffect(() => { - const fetchedArchitecture = { - id: params.id, - title: 'Architecture for Cloud Canvas', - author: 'Web37-team', - createdAt: new Date(), - architecture: 'Architecture Content', - stars: 4, - imports: 2, - }; - setArchitecture(fetchedArchitecture); - }, []); + if (isLoading) return ; + if (error) return ; + + const { + title, + author: { name: author }, + createdAt, + cost, + tags, + stars: starData, + _count: { stars, imports }, + } = data!; + + const isStarred = starData?.length > 0; + const isLoggedIn = localStorage.getItem('isLoggedIn') !== null; - const handleImport = () => { - console.log('Imported!'); + const toggleStar = async () => { + await fetch( + `http://localhost:3000/public-architectures/${params.id}/stars`, + { + method: data!.stars.length > 0 ? 'DELETE' : 'POST', + credentials: 'include', + }, + ); + mutate({ ...data!, stars: data!.stars.length > 0 ? [] : [{}] }); + }; + + const handleImport = async () => { + await fetch( + `http://localhost:3000/public-architectures/${params.id}/imports`, + { + method: 'POST', + credentials: 'include', + }, + ); + mutate({ + ...data!, + _count: { ...data!._count, imports: data!._count.imports + 1 }, + }); + alert('Imported!'); }; return (
    -

    - {architecture.title} -

    +
    +
    + {tags.map(({ tag: { name } }) => ( + + ))} +
    +

    {title}

    +
    by - - {architecture.author} - + {author}
    -
    {architecture.createdAt?.toLocaleString()}
    +
    {new Date(createdAt).toLocaleString()}
    - - {architecture.imports} - + {imports} imported
    -
    - - +
    +
    + + ${cost} + + / month +
    + +

    +
    {JSON.stringify(data, null, 2)}
    ); @@ -78,12 +128,12 @@ const ArchitectureImageExample = () => ( fill="none" xmlns="http://www.w3.org/2000/svg" > - + ); diff --git a/apps/hub/src/app/my/architectures/page.tsx b/apps/hub/src/app/my/architectures/page.tsx index c1cf92a1..f278801b 100644 --- a/apps/hub/src/app/my/architectures/page.tsx +++ b/apps/hub/src/app/my/architectures/page.tsx @@ -1,5 +1,11 @@ -import { Architectures } from '@/components/Architectures'; +'use client'; +import { ArchitectureBoard } from '@/components/ArchitectureBoard'; +import { Suspense } from 'react'; export default function MyArchitecturesPage() { - return ; + return ( + + + + ); } diff --git a/apps/hub/src/app/my/shared/page.tsx b/apps/hub/src/app/my/shared/page.tsx index 81e94ad2..a5926b0f 100644 --- a/apps/hub/src/app/my/shared/page.tsx +++ b/apps/hub/src/app/my/shared/page.tsx @@ -1,5 +1,11 @@ -import { Architectures } from '@/components/Architectures'; +'use client'; +import { ArchitectureBoard } from '@/components/ArchitectureBoard'; +import { Suspense } from 'react'; export default function MySharedPage() { - return ; + return ( + + + + ); } diff --git a/apps/hub/src/app/my/starred/page.tsx b/apps/hub/src/app/my/starred/page.tsx index 9d432eec..21700778 100644 --- a/apps/hub/src/app/my/starred/page.tsx +++ b/apps/hub/src/app/my/starred/page.tsx @@ -1,5 +1,11 @@ -import { Architectures } from '@/components/Architectures'; +'use client'; +import { ArchitectureBoard } from '@/components/ArchitectureBoard'; +import { Suspense } from 'react'; export default function MyStarredPage() { - return ; + return ( + + + + ); } diff --git a/apps/hub/src/app/page.tsx b/apps/hub/src/app/page.tsx index 6777118c..61c42ee4 100644 --- a/apps/hub/src/app/page.tsx +++ b/apps/hub/src/app/page.tsx @@ -1,6 +1,11 @@ 'use client'; import { ArchitectureBoard } from '@/components/ArchitectureBoard'; +import { Suspense } from 'react'; export default function Home() { - return ; + return ( + + + + ); } From e9fc8f94174c26c417c950b51eb0b5d891661b07 Mon Sep 17 00:00:00 2001 From: paulcjy Date: Thu, 28 Nov 2024 13:01:43 +0900 Subject: [PATCH 021/124] =?UTF-8?q?=F0=9F=9A=9A=20Setting(hub):=20?= =?UTF-8?q?=EB=B9=8C=EB=93=9C=20=EC=8B=9C=20any=20=ED=83=80=EC=9E=85=20?= =?UTF-8?q?=ED=97=88=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D=20eslint=20?= =?UTF-8?q?=EC=98=B5=EC=85=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/hub/.eslintrc.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/hub/.eslintrc.json b/apps/hub/.eslintrc.json index 98ef693e..ee7d699d 100644 --- a/apps/hub/.eslintrc.json +++ b/apps/hub/.eslintrc.json @@ -1,3 +1,6 @@ { - "extends": ["next/core-web-vitals", "next/typescript"] + "extends": ["next/core-web-vitals", "next/typescript"], + "rules": { + "@typescript-eslint/no-explicit-any": "off" + } } From 772b5e8ecc610e03932b1c6e240b9276af0cfb8c Mon Sep 17 00:00:00 2001 From: paulcjy Date: Thu, 28 Nov 2024 13:02:37 +0900 Subject: [PATCH 022/124] =?UTF-8?q?=F0=9F=9A=9A=20Setting(server):=20prism?= =?UTF-8?q?a=20migrate=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{20241125182942_init => 20241127171428_init}/migration.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename apps/server/prisma/migrations/{20241125182942_init => 20241127171428_init}/migration.sql (97%) diff --git a/apps/server/prisma/migrations/20241125182942_init/migration.sql b/apps/server/prisma/migrations/20241127171428_init/migration.sql similarity index 97% rename from apps/server/prisma/migrations/20241125182942_init/migration.sql rename to apps/server/prisma/migrations/20241127171428_init/migration.sql index a4abcef0..e9c664da 100644 --- a/apps/server/prisma/migrations/20241125182942_init/migration.sql +++ b/apps/server/prisma/migrations/20241127171428_init/migration.sql @@ -5,7 +5,6 @@ CREATE TABLE `import` ( `user_id` INTEGER NOT NULL, `created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0), - UNIQUE INDEX `import_public_architecture_id_user_id_key`(`public_architecture_id`, `user_id`), PRIMARY KEY (`id`) ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; @@ -61,6 +60,7 @@ CREATE TABLE `tag` ( `name` VARCHAR(15) NOT NULL, `created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0), + UNIQUE INDEX `tag_name_key`(`name`), PRIMARY KEY (`id`) ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; From 4076d8633544e039729d060486cdb9ac909a0846 Mon Sep 17 00:00:00 2001 From: paulcjy Date: Thu, 28 Nov 2024 13:03:53 +0900 Subject: [PATCH 023/124] =?UTF-8?q?=F0=9F=9A=9A=20Setting(ncloud-sdk):=20?= =?UTF-8?q?=ED=8C=A8=ED=82=A4=EC=A7=80=EB=A5=BC=20=EC=A0=95=EC=83=81?= =?UTF-8?q?=EC=A0=81=EC=9C=BC=EB=A1=9C=20import=ED=95=A0=20=EC=88=98=20?= =?UTF-8?q?=EC=9E=88=EB=8F=84=EB=A1=9D=20package.json=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/server/package.json | 3 ++- packages/ncloud-sdk/package.json | 13 +++++++++++++ pnpm-lock.yaml | 21 ++++++++++++--------- 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/apps/server/package.json b/apps/server/package.json index 6edfd603..b07cdbc0 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -39,7 +39,8 @@ "prisma": "^5.22.0", "reflect-metadata": "^0.2.0", "rxjs": "^7.8.1", - "@faker-js/faker": "^9.2.0" + "@faker-js/faker": "^9.2.0", + "@cloud-canvas/ncloud-sdk": "workspace:*" }, "devDependencies": { "@nestjs/cli": "^10.0.0", diff --git a/packages/ncloud-sdk/package.json b/packages/ncloud-sdk/package.json index b4159d89..bb89ec0a 100644 --- a/packages/ncloud-sdk/package.json +++ b/packages/ncloud-sdk/package.json @@ -23,5 +23,18 @@ "dotenv": "^16.4.5", "tsup": "^8.3.5", "tsx": "^4.19.2" + }, + "exports": { + ".": { + "import": "./dist/index.mjs", + "require": "./dist/index.js" + } + }, + "typesVersions": { + "*": { + "*": [ + "src/*" + ] + } } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 983113d3..77175340 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -121,6 +121,9 @@ importers: apps/server: dependencies: + '@cloud-canvas/ncloud-sdk': + specifier: workspace:* + version: link:../../packages/ncloud-sdk '@faker-js/faker': specifier: ^9.2.0 version: 9.2.0 @@ -9012,8 +9015,8 @@ snapshots: '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.6.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1) eslint-plugin-react: 7.37.2(eslint@8.57.1) eslint-plugin-react-hooks: 5.0.0(eslint@8.57.1) @@ -9036,37 +9039,37 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1): + eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.3.7 enhanced-resolve: 5.17.1 eslint: 8.57.1 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) fast-glob: 3.3.2 get-tsconfig: 4.8.1 is-bun-module: 1.2.1 is-glob: 4.0.3 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) transitivePeerDependencies: - '@typescript-eslint/parser' - eslint-import-resolver-node - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): + eslint-module-utils@2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.6.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -9077,7 +9080,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 From 37c37a615952557e04b5bd9e7045abc6ab43ebdf Mon Sep 17 00:00:00 2001 From: paulcjy Date: Thu, 28 Nov 2024 13:45:09 +0900 Subject: [PATCH 024/124] =?UTF-8?q?=F0=9F=9A=9A=20Setting(cli):=20cli=20?= =?UTF-8?q?=EB=B9=8C=EB=93=9C=20=EC=98=B5=EC=85=98=EC=97=90=EC=84=9C=20dep?= =?UTF-8?q?endency=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/cli/turbo.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/cli/turbo.json b/packages/cli/turbo.json index 0ea421de..eb40a6ff 100644 --- a/packages/cli/turbo.json +++ b/packages/cli/turbo.json @@ -10,10 +10,6 @@ "persistent": true }, "build": { - "dependsOn": [ - "@cloud-canvas/client#bundle", - "@cloud-canvas/server#bundle" - ], "inputs": ["$TURBO_DEFAULT$", "!README.md", "!CHANGELOG.md"], "outputs": ["dist/**"] } From 6f97d50b448321d38b637020fe55f0ce6801949c Mon Sep 17 00:00:00 2001 From: Geonhyuk Seo Date: Thu, 28 Nov 2024 16:50:59 +0900 Subject: [PATCH 025/124] =?UTF-8?q?=E2=9C=A8=20Feat(server):=20ncloud=20se?= =?UTF-8?q?rver=20price=EC=97=90=20=EB=8C=80=ED=95=B4=EC=84=9C=20=EB=A7=B5?= =?UTF-8?q?=20=EA=B0=9D=EC=B2=B4=EB=A1=9C=20=EB=B0=9B=EC=9D=84=20=EC=88=98?= =?UTF-8?q?=20=EC=9E=88=EB=8F=84=EB=A1=9D=20=EA=B8=B0=EB=8A=A5=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=99=84?= =?UTF-8?q?=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ISSUES CLOSED: #140 --- apps/server/src/main.ts | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/apps/server/src/main.ts b/apps/server/src/main.ts index 5de817bd..f90a5298 100644 --- a/apps/server/src/main.ts +++ b/apps/server/src/main.ts @@ -42,21 +42,30 @@ async function bootstrap() { payCurrencyCode: 'KRW', productCategoryCode: 'COMPUTE', }); + const map: Map[]> = new Map(); result.productPriceList.forEach((product) => { if ( product.productItemKind.code === 'SVR' && product.serverProductCode && product.serverProductCode.endsWith('50') ) { - console.log(product.productType.codeName); - console.log(product.serverProductCode); - console.log('월'); - console.log(product.priceList[0].price); - console.log('시간'); - console.log(product.priceList[1].price); - console.log('gksk'); + if (!map.has(product.productType.codeName)) + map.set(product.productType.codeName, []); + const { + serverProductCode, + priceList: [{ price: monthPrice }, { price: hourPrice }], + }: { + serverProductCode: string; + priceList: { price: number }[]; + } = product; + map.get(product.productType.codeName).push({ + serverProductCode: serverProductCode.toLowerCase(), + monthPrice, + hourPrice, + }); } }); + console.log(map); swaggerConfig(app); From 193e50b3a5809f319b062b1ab25cb46a0f243162 Mon Sep 17 00:00:00 2001 From: Geonhyuk Seo Date: Thu, 28 Nov 2024 17:14:38 +0900 Subject: [PATCH 026/124] =?UTF-8?q?=E2=9C=A8=20Feat(server):=20=ED=81=AC?= =?UTF-8?q?=EB=A1=A0=20=EC=9E=A1=201=EC=9D=BC=EB=A7=88=EB=8B=A4=20?= =?UTF-8?q?=ED=95=9C=20=EB=B2=88=EC=94=A9=20=EB=8F=8C=EC=95=84=EA=B0=80?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=84=A4=EC=A0=95=20=EB=B0=8F=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ISSUES CLOSED: #140 --- apps/server/src/main.ts | 66 +++++++++---------- .../ncloud-resources.service.ts | 17 ++++- 2 files changed, 49 insertions(+), 34 deletions(-) diff --git a/apps/server/src/main.ts b/apps/server/src/main.ts index f90a5298..fd0ac62b 100644 --- a/apps/server/src/main.ts +++ b/apps/server/src/main.ts @@ -10,10 +10,10 @@ import { ApiKeyCredentials, Ncloud, PriceApi } from '@cloud-canvas/ncloud-sdk'; async function bootstrap() { const app = await NestFactory.create(AppModule); - const ncloud = new Ncloud(); - const priceApi = new PriceApi(ncloud.keys() as ApiKeyCredentials); - const result1 = await priceApi.getProductCategoryList({}); - console.log(result1.productCategoryList); + // const ncloud = new Ncloud(); + // const priceApi = new PriceApi(ncloud.keys() as ApiKeyCredentials); + // const result1 = await priceApi.getProductCategoryList({}); + // console.log(result1.productCategoryList); // VPC // const result = await priceApi.getProductList({ @@ -37,35 +37,35 @@ async function bootstrap() { // }) // SERVER PRICE - const result = await priceApi.getProductPriceList({ - regionCode: 'KR', - payCurrencyCode: 'KRW', - productCategoryCode: 'COMPUTE', - }); - const map: Map[]> = new Map(); - result.productPriceList.forEach((product) => { - if ( - product.productItemKind.code === 'SVR' && - product.serverProductCode && - product.serverProductCode.endsWith('50') - ) { - if (!map.has(product.productType.codeName)) - map.set(product.productType.codeName, []); - const { - serverProductCode, - priceList: [{ price: monthPrice }, { price: hourPrice }], - }: { - serverProductCode: string; - priceList: { price: number }[]; - } = product; - map.get(product.productType.codeName).push({ - serverProductCode: serverProductCode.toLowerCase(), - monthPrice, - hourPrice, - }); - } - }); - console.log(map); + // const result = await priceApi.getProductPriceList({ + // regionCode: 'KR', + // payCurrencyCode: 'KRW', + // productCategoryCode: 'COMPUTE', + // }); + // const map: Map[]> = new Map(); + // result.productPriceList.forEach((product) => { + // if ( + // product.productItemKind.code === 'SVR' && + // product.serverProductCode && + // product.serverProductCode.endsWith('50') + // ) { + // if (!map.has(product.productType.codeName)) + // map.set(product.productType.codeName, []); + // const { + // serverProductCode, + // priceList: [{ price: monthPrice }, { price: hourPrice }], + // }: { + // serverProductCode: string; + // priceList: { price: number }[]; + // } = product; + // map.get(product.productType.codeName).push({ + // serverProductCode: serverProductCode.toLowerCase(), + // monthPrice, + // hourPrice, + // }); + // } + // }); + // console.log(map); swaggerConfig(app); diff --git a/apps/server/src/ncloud-resources/ncloud-resources.service.ts b/apps/server/src/ncloud-resources/ncloud-resources.service.ts index 227d0c88..71686d65 100644 --- a/apps/server/src/ncloud-resources/ncloud-resources.service.ts +++ b/apps/server/src/ncloud-resources/ncloud-resources.service.ts @@ -1,4 +1,19 @@ import { Injectable } from '@nestjs/common'; +import { Cron, CronExpression } from '@nestjs/schedule'; +import { PrismaService } from 'src/prisma/prisma.service'; @Injectable() -export class NcloudResourcesService {} +export class NcloudResourcesService { + constructor(private readonly prisma: PrismaService) {} + + @Cron(CronExpression.EVERY_DAY_AT_3AM, { + name: 'Insert Ncloud Resource Cron Job', + }) + async insertNcloudResource() { + console.log(await this.prisma.star.findMany({})); + } + + onApplicationBootstrap() { + this.insertNcloudResource(); + } +} From e6a84b3df176482fcb95f51a6fd3b7ed5924df8e Mon Sep 17 00:00:00 2001 From: paulcjy Date: Thu, 28 Nov 2024 17:30:53 +0900 Subject: [PATCH 027/124] =?UTF-8?q?=F0=9F=A4=96=20Refactor(hub):=20global?= =?UTF-8?q?=20header=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=EB=A5=BC=20?= =?UTF-8?q?=ED=8F=B4=EB=8D=94=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/{GlobalHeader.tsx => GlobalHeader/index.tsx} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename apps/hub/src/components/{GlobalHeader.tsx => GlobalHeader/index.tsx} (98%) diff --git a/apps/hub/src/components/GlobalHeader.tsx b/apps/hub/src/components/GlobalHeader/index.tsx similarity index 98% rename from apps/hub/src/components/GlobalHeader.tsx rename to apps/hub/src/components/GlobalHeader/index.tsx index 5b072e49..c95ac969 100644 --- a/apps/hub/src/components/GlobalHeader.tsx +++ b/apps/hub/src/components/GlobalHeader/index.tsx @@ -3,7 +3,7 @@ import Link from 'next/link'; import { useRouter } from 'next/navigation'; import { useEffect, useState } from 'react'; -import { LinkButton } from '../ui/LinkButton'; +import { LinkButton } from '@/ui/LinkButton'; export const GlobalHeader = () => { const router = useRouter(); From 72bb22affa10c55fa089aec81e5fc5f9b9d8beaa Mon Sep 17 00:00:00 2001 From: paulcjy Date: Thu, 28 Nov 2024 17:31:39 +0900 Subject: [PATCH 028/124] =?UTF-8?q?=F0=9F=90=9B=20Design(hub):=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=EB=84=A4=EC=9D=B4=EC=85=98=EC=97=90=EC=84=9C?= =?UTF-8?q?=205=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=9D=B4=ED=95=98=EB=8A=94?= =?UTF-8?q?=20=EC=88=AB=EC=9E=90=EA=B0=80=20=EC=A0=84=EB=B6=80=20=ED=91=9C?= =?UTF-8?q?=EC=8B=9C=EB=90=98=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/hub/src/components/Pagination/index.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/hub/src/components/Pagination/index.tsx b/apps/hub/src/components/Pagination/index.tsx index 35025728..264d0e19 100644 --- a/apps/hub/src/components/Pagination/index.tsx +++ b/apps/hub/src/components/Pagination/index.tsx @@ -15,6 +15,10 @@ export const Pagination = ({ if (totalPages <= 1) return null; const getVisiblePages = () => { + if (totalPages <= 5) { + return Array.from({ length: totalPages }, (_, i) => i + 1); + } + const delta = 2; const range = []; const rangeWithDots = []; From 99a0cb34f4a09a21e5d35dd9d4aa24329ad66015 Mon Sep 17 00:00:00 2001 From: paulcjy Date: Thu, 28 Nov 2024 17:32:16 +0900 Subject: [PATCH 029/124] =?UTF-8?q?=E2=9C=A8=20Feat(hub):=20my=20private?= =?UTF-8?q?=20architectures=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EA=B8=B0?= =?UTF-8?q?=EB=B3=B8=20=ED=8B=80=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/hub/src/app/my/architectures/page.tsx | 55 +++++++++++++++++++++- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/apps/hub/src/app/my/architectures/page.tsx b/apps/hub/src/app/my/architectures/page.tsx index f278801b..b8283ae2 100644 --- a/apps/hub/src/app/my/architectures/page.tsx +++ b/apps/hub/src/app/my/architectures/page.tsx @@ -1,11 +1,62 @@ 'use client'; -import { ArchitectureBoard } from '@/components/ArchitectureBoard'; +import { Pagination } from '@/components/Pagination'; +import { SearchBar } from '@/components/SearchBar'; +import { useQueryData } from '@/hooks/useQueryData'; +import { useQueryParams } from '@/hooks/useQueryParams'; +import { ErrorMessage } from '@/ui/ErrorMessage'; +import { LoadingSpinner } from '@/ui/LoadingSpinner'; +import { calculateTotalPages } from '@/utils/pagination'; +import Link from 'next/link'; import { Suspense } from 'react'; export default function MyArchitecturesPage() { + const { params, setParams } = useQueryParams(); + const { data, total, isLoading, error } = useQueryData( + 'http://localhost:3000/my/private-architectures', + params, + ); + + const handleSearch = (keyword: string) => + setParams({ search: keyword, page: 1, sort: '', order: '' }); + + const handlePageChange = (page: number) => setParams({ page }); + + if (error) return ; + return ( - +
    + + {/* 헤더 추가하고 아이템 너비 맞추기 */} + {isLoading ? ( + + ) : ( + <> +
    + {data.map((item) => ( +
  • + +

    + {item.title} +

    + +
    {item.cost}
    +
    {item.createdAt}
    +
    {item.updatedAt}
    +
  • + ))} + + + + )} + ); } From cad29ece3c271a742c15cca0cc6cd67aa42a6f7d Mon Sep 17 00:00:00 2001 From: Geonhyuk Seo Date: Thu, 28 Nov 2024 18:02:53 +0900 Subject: [PATCH 030/124] =?UTF-8?q?=E2=9C=A8=20Feat(server):=20ncloud=20se?= =?UTF-8?q?rver=20=EB=A6=AC=EC=86=8C=EC=8A=A4=20=EA=B0=80=EA=B2=A9=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EB=B0=B0=EC=B9=98=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20=EB=B0=8F=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ISSUES CLOSED: #140 --- .../migrations/20241128085427_/migration.sql | 137 ++++++++++++++++++ .../prisma/migrations/migration_lock.toml | 3 + .../schema/ncloud-server-resoruce.prisma | 2 +- apps/server/src/main.ts | 57 -------- .../ncloud-resources.service.ts | 90 +++++++++++- 5 files changed, 228 insertions(+), 61 deletions(-) create mode 100644 apps/server/prisma/migrations/20241128085427_/migration.sql create mode 100644 apps/server/prisma/migrations/migration_lock.toml diff --git a/apps/server/prisma/migrations/20241128085427_/migration.sql b/apps/server/prisma/migrations/20241128085427_/migration.sql new file mode 100644 index 00000000..b9941b71 --- /dev/null +++ b/apps/server/prisma/migrations/20241128085427_/migration.sql @@ -0,0 +1,137 @@ +-- CreateTable +CREATE TABLE `import` ( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `public_architecture_id` INTEGER NOT NULL, + `user_id` INTEGER NOT NULL, + `created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0), + + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `ncloud_server_resource` ( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `server_resource_type_id` INTEGER NOT NULL, + `server_spec_code` CHAR(50) NOT NULL, + `hour_cost` DOUBLE NOT NULL, + `month_cost` DOUBLE NOT NULL, + `created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0), + `updated_at` TIMESTAMP(0) NULL, + + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `ncloud_server_resource_type` ( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `type` CHAR(50) NOT NULL, + + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `private_architecture` ( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `title` CHAR(50) NOT NULL, + `author_id` INTEGER NOT NULL, + `architecture` JSON NOT NULL, + `cost` DOUBLE NOT NULL DEFAULT 0, + `created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0), + `updated_at` TIMESTAMP(0) NULL, + + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `public_architecture_tag` ( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `public_architecture_id` INTEGER NOT NULL, + `tag_id` INTEGER NOT NULL, + + UNIQUE INDEX `public_architecture_tag_public_architecture_id_tag_id_key`(`public_architecture_id`, `tag_id`), + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `public_architecture` ( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `title` CHAR(50) NOT NULL, + `author_id` INTEGER NOT NULL, + `architecture` JSON NOT NULL, + `created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0), + `cost` DOUBLE NOT NULL DEFAULT 0, + + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `star` ( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `public_architecture_id` INTEGER NOT NULL, + `user_id` INTEGER NOT NULL, + `created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0), + + UNIQUE INDEX `star_public_architecture_id_user_id_key`(`public_architecture_id`, `user_id`), + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `tag` ( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `name` VARCHAR(15) NOT NULL, + `created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0), + + UNIQUE INDEX `tag_name_key`(`name`), + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `user` ( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `name` VARCHAR(30) NOT NULL, + `created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0), + + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `version` ( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `private_architecture_id` INTEGER NOT NULL, + `title` CHAR(50) NOT NULL, + `created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0), + `architecture` JSON NOT NULL, + `cost` DOUBLE NOT NULL DEFAULT 0, + + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- AddForeignKey +ALTER TABLE `import` ADD CONSTRAINT `import_public_architecture_id_fkey` FOREIGN KEY (`public_architecture_id`) REFERENCES `public_architecture`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `import` ADD CONSTRAINT `import_user_id_fkey` FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `ncloud_server_resource` ADD CONSTRAINT `ncloud_server_resource_server_resource_type_id_fkey` FOREIGN KEY (`server_resource_type_id`) REFERENCES `ncloud_server_resource_type`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `private_architecture` ADD CONSTRAINT `private_architecture_author_id_fkey` FOREIGN KEY (`author_id`) REFERENCES `user`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `public_architecture_tag` ADD CONSTRAINT `public_architecture_tag_public_architecture_id_fkey` FOREIGN KEY (`public_architecture_id`) REFERENCES `public_architecture`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `public_architecture_tag` ADD CONSTRAINT `public_architecture_tag_tag_id_fkey` FOREIGN KEY (`tag_id`) REFERENCES `tag`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `public_architecture` ADD CONSTRAINT `public_architecture_author_id_fkey` FOREIGN KEY (`author_id`) REFERENCES `user`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `star` ADD CONSTRAINT `star_public_architecture_id_fkey` FOREIGN KEY (`public_architecture_id`) REFERENCES `public_architecture`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `star` ADD CONSTRAINT `star_user_id_fkey` FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `version` ADD CONSTRAINT `version_private_architecture_id_fkey` FOREIGN KEY (`private_architecture_id`) REFERENCES `private_architecture`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/apps/server/prisma/migrations/migration_lock.toml b/apps/server/prisma/migrations/migration_lock.toml new file mode 100644 index 00000000..e5a788a7 --- /dev/null +++ b/apps/server/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) +provider = "mysql" \ No newline at end of file diff --git a/apps/server/prisma/schema/ncloud-server-resoruce.prisma b/apps/server/prisma/schema/ncloud-server-resoruce.prisma index c56dc35e..773ecf15 100644 --- a/apps/server/prisma/schema/ncloud-server-resoruce.prisma +++ b/apps/server/prisma/schema/ncloud-server-resoruce.prisma @@ -3,7 +3,7 @@ model NcloudServerResource { serverResourceTypeId Int @map("server_resource_type_id") serverSpecCode String @map("server_spec_code") @db.Char(50) hourCost Float @map("hour_cost") - timeCost Float @map("month_cost") + monthCost Float @map("month_cost") createdAt DateTime @default(now()) @map("created_at") @db.Timestamp(0) updatedAt DateTime? @updatedAt @map("updated_at") @db.Timestamp(0) diff --git a/apps/server/src/main.ts b/apps/server/src/main.ts index fd0ac62b..f661715c 100644 --- a/apps/server/src/main.ts +++ b/apps/server/src/main.ts @@ -6,66 +6,9 @@ import helmet from 'helmet'; import * as cookieParser from 'cookie-parser'; import { PrismaExceptionFilter } from './filters/prisma-exception.filter'; import { ConfigService } from '@nestjs/config'; -import { ApiKeyCredentials, Ncloud, PriceApi } from '@cloud-canvas/ncloud-sdk'; async function bootstrap() { const app = await NestFactory.create(AppModule); - // const ncloud = new Ncloud(); - // const priceApi = new PriceApi(ncloud.keys() as ApiKeyCredentials); - // const result1 = await priceApi.getProductCategoryList({}); - // console.log(result1.productCategoryList); - - // VPC - // const result = await priceApi.getProductList({ - // regionCode: 'KR', - // productCategoryCode: 'NETWORKING' - // }); - // result.productList.forEach((product) => { - // if(product.productCode === 'VPC.V001' || product.productCode === 'SPNET00000000015') - // console.log(product); - // }) - - // SERVER - // const result = await priceApi.getProductList({ - // regionCode: 'KR', - // productCategoryCode: 'COMPUTE', - // }); - // result.productList.forEach((product) => { - // if(product.productItemKind.codeName === 'Server' && product.serverProductCode){ - // console.log(product); - // } - // }) - - // SERVER PRICE - // const result = await priceApi.getProductPriceList({ - // regionCode: 'KR', - // payCurrencyCode: 'KRW', - // productCategoryCode: 'COMPUTE', - // }); - // const map: Map[]> = new Map(); - // result.productPriceList.forEach((product) => { - // if ( - // product.productItemKind.code === 'SVR' && - // product.serverProductCode && - // product.serverProductCode.endsWith('50') - // ) { - // if (!map.has(product.productType.codeName)) - // map.set(product.productType.codeName, []); - // const { - // serverProductCode, - // priceList: [{ price: monthPrice }, { price: hourPrice }], - // }: { - // serverProductCode: string; - // priceList: { price: number }[]; - // } = product; - // map.get(product.productType.codeName).push({ - // serverProductCode: serverProductCode.toLowerCase(), - // monthPrice, - // hourPrice, - // }); - // } - // }); - // console.log(map); swaggerConfig(app); diff --git a/apps/server/src/ncloud-resources/ncloud-resources.service.ts b/apps/server/src/ncloud-resources/ncloud-resources.service.ts index 71686d65..e8a402be 100644 --- a/apps/server/src/ncloud-resources/ncloud-resources.service.ts +++ b/apps/server/src/ncloud-resources/ncloud-resources.service.ts @@ -1,3 +1,4 @@ +import { Ncloud, PriceApi, ApiKeyCredentials } from '@cloud-canvas/ncloud-sdk'; import { Injectable } from '@nestjs/common'; import { Cron, CronExpression } from '@nestjs/schedule'; import { PrismaService } from 'src/prisma/prisma.service'; @@ -10,10 +11,93 @@ export class NcloudResourcesService { name: 'Insert Ncloud Resource Cron Job', }) async insertNcloudResource() { - console.log(await this.prisma.star.findMany({})); + const ncloud = new Ncloud(); + const priceApi = new PriceApi(ncloud.keys() as ApiKeyCredentials); + const result = await priceApi.getProductPriceList({ + regionCode: 'KR', + payCurrencyCode: 'KRW', + productCategoryCode: 'COMPUTE', + }); + const ncloudServerResourceMap: Map< + string, + Record[] + > = new Map(); + result.productPriceList.forEach((product) => { + if ( + product.productItemKind.code === 'SVR' && + product.serverProductCode && + product.serverProductCode.endsWith('50') + ) { + if (!ncloudServerResourceMap.has(product.productType.codeName)) + ncloudServerResourceMap.set( + product.productType.codeName, + [], + ); + const { + serverProductCode, + priceList: [{ price: monthPrice }, { price: hourPrice }], + }: { + serverProductCode: string; + priceList: { price: number }[]; + } = product; + ncloudServerResourceMap.get(product.productType.codeName).push({ + serverProductCode: serverProductCode.toLowerCase(), + monthPrice, + hourPrice, + }); + } + }); + await this.prisma.$transaction(async (tx) => { + await tx.ncloudServerResource.deleteMany({}); + await tx.ncloudServerResourceType.deleteMany({}); + + const ncloudServerResourceTypes = [ + ...ncloudServerResourceMap.keys(), + ].map((key) => ({ type: key })); + + await tx.ncloudServerResourceType.createMany({ + data: ncloudServerResourceTypes, + }); + + const ncloudServerResources = await Promise.all( + [...ncloudServerResourceMap.values()].map( + async (ncloudServerResourceList, index) => { + const serverResourceTypeId = + await tx.ncloudServerResourceType.findFirst({ + select: { id: true }, + where: { + type: ncloudServerResourceTypes[index].type, + }, + }); + + return ncloudServerResourceList.map( + (ncloudServerResource) => ({ + serverResourceTypeId: serverResourceTypeId?.id, + serverSpecCode: + ncloudServerResource.serverProductCode as string, + hourCost: parseFloat( + '' + ncloudServerResource.hourPrice, + ), + monthCost: parseFloat( + '' + ncloudServerResource.monthPrice, + ), + }), + ); + }, + ), + ); + + const flattenedResources = ncloudServerResources.flat(); + + await tx.ncloudServerResource.createMany({ + data: flattenedResources, + }); + console.log(await tx.ncloudServerResourceType.findMany({})); + console.log(await tx.ncloudServerResource.findMany({})); + }); } - onApplicationBootstrap() { - this.insertNcloudResource(); + async onApplicationBootstrap() { + await this.insertNcloudResource(); } } From e8642dfa49ac74550599f4d8fad90c00af02567d Mon Sep 17 00:00:00 2001 From: paulcjy Date: Fri, 29 Nov 2024 00:30:11 +0900 Subject: [PATCH 031/124] =?UTF-8?q?=F0=9F=90=9E=20Fix(hub):=20my=20private?= =?UTF-8?q?=20architectures=20=ED=8E=98=EC=9D=B4=EC=A7=80=EC=97=90?= =?UTF-8?q?=EC=84=9C=20useSearchParams=20suspense=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/hub/src/app/my/architectures/page.tsx | 56 +--------------- .../PrivateArchitectureBoard/index.tsx | 64 +++++++++++++++++++ 2 files changed, 67 insertions(+), 53 deletions(-) create mode 100644 apps/hub/src/components/PrivateArchitectureBoard/index.tsx diff --git a/apps/hub/src/app/my/architectures/page.tsx b/apps/hub/src/app/my/architectures/page.tsx index b8283ae2..00995c43 100644 --- a/apps/hub/src/app/my/architectures/page.tsx +++ b/apps/hub/src/app/my/architectures/page.tsx @@ -1,62 +1,12 @@ 'use client'; -import { Pagination } from '@/components/Pagination'; -import { SearchBar } from '@/components/SearchBar'; -import { useQueryData } from '@/hooks/useQueryData'; -import { useQueryParams } from '@/hooks/useQueryParams'; -import { ErrorMessage } from '@/ui/ErrorMessage'; -import { LoadingSpinner } from '@/ui/LoadingSpinner'; -import { calculateTotalPages } from '@/utils/pagination'; -import Link from 'next/link'; +import { PrivateArchitectureBoard } from '@/components/PrivateArchitectureBoard'; + import { Suspense } from 'react'; export default function MyArchitecturesPage() { - const { params, setParams } = useQueryParams(); - const { data, total, isLoading, error } = useQueryData( - 'http://localhost:3000/my/private-architectures', - params, - ); - - const handleSearch = (keyword: string) => - setParams({ search: keyword, page: 1, sort: '', order: '' }); - - const handlePageChange = (page: number) => setParams({ page }); - - if (error) return ; - return ( -
    - - {/* 헤더 추가하고 아이템 너비 맞추기 */} - {isLoading ? ( - - ) : ( - <> -
    - {data.map((item) => ( -
  • - -

    - {item.title} -

    - -
    {item.cost}
    -
    {item.createdAt}
    -
    {item.updatedAt}
    -
  • - ))} -
    - - - )} -
    +
    ); } diff --git a/apps/hub/src/components/PrivateArchitectureBoard/index.tsx b/apps/hub/src/components/PrivateArchitectureBoard/index.tsx new file mode 100644 index 00000000..d738854b --- /dev/null +++ b/apps/hub/src/components/PrivateArchitectureBoard/index.tsx @@ -0,0 +1,64 @@ +'use client'; +import { Pagination } from '@/components/Pagination'; +import { SearchBar } from '@/components/SearchBar'; +import { useQueryData } from '@/hooks/useQueryData'; +import { useQueryParams } from '@/hooks/useQueryParams'; +import { ErrorMessage } from '@/ui/ErrorMessage'; +import { LoadingSpinner } from '@/ui/LoadingSpinner'; +import { calculateTotalPages } from '@/utils/pagination'; +import Link from 'next/link'; +import { Suspense } from 'react'; + +export const PrivateArchitectureBoard = ({ apiUrl }: { apiUrl: string }) => { + const { params, setParams } = useQueryParams(); + const { data, total, isLoading, error } = useQueryData(apiUrl, params); + + const handleSearch = (keyword: string) => + setParams({ search: keyword, page: 1, sort: '', order: '' }); + + const handlePageChange = (page: number) => setParams({ page }); + + if (error) + return ( + + + + ); + + return ( + +
    + + {/* 헤더 추가하고 아이템 너비 맞추기 */} + {isLoading ? ( + + ) : ( + <> +
    + {data.map((item: any) => ( +
  • + +

    + {item.title} +

    + +
    {item.cost}
    +
    {item.createdAt}
    +
    {item.updatedAt}
    +
  • + ))} +
    + + + )} +
    +
    + ); +}; From f6c3dd8297d137a2085410bc299ea448c9bc7801 Mon Sep 17 00:00:00 2001 From: Geonhyuk Seo Date: Fri, 29 Nov 2024 01:00:08 +0900 Subject: [PATCH 032/124] =?UTF-8?q?=F0=9F=9A=9A=20Setting(hub):=20?= =?UTF-8?q?=EC=84=9C=EB=B2=84=20url=EC=9D=84=20env=EB=A1=9C=20=EB=B0=9B?= =?UTF-8?q?=EC=9D=84=20=EC=88=98=20=EC=9E=88=EB=8F=84=EB=A1=9D=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/hub/package.json | 2 +- apps/hub/src/app/architectures/[id]/page.tsx | 7 +++---- apps/hub/src/app/my/architectures/page.tsx | 4 +++- apps/hub/src/app/my/shared/page.tsx | 4 +++- apps/hub/src/app/my/starred/page.tsx | 4 +++- apps/hub/src/app/page.tsx | 4 +++- apps/hub/src/components/GlobalHeader/index.tsx | 13 ++++++++----- 7 files changed, 24 insertions(+), 14 deletions(-) diff --git a/apps/hub/package.json b/apps/hub/package.json index d019b5b7..9ba2b0df 100644 --- a/apps/hub/package.json +++ b/apps/hub/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "next dev", + "dev": "next dev -p 3001", "build": "next build", "start": "next start", "lint": "next lint" diff --git a/apps/hub/src/app/architectures/[id]/page.tsx b/apps/hub/src/app/architectures/[id]/page.tsx index 0096959f..2688335a 100644 --- a/apps/hub/src/app/architectures/[id]/page.tsx +++ b/apps/hub/src/app/architectures/[id]/page.tsx @@ -26,7 +26,7 @@ interface PublicArchitecture { export default function ArchitectureDetailPage() { const params = useParams<{ id: string }>(); const { data, error, isLoading, mutate } = useSWR( - `http://localhost:3000/public-architectures/${params.id}`, + `${process.env.NEXT_PUBLIC_BACK_URL}/public-architectures/${params.id}`, fetcher, ); @@ -48,7 +48,7 @@ export default function ArchitectureDetailPage() { const toggleStar = async () => { await fetch( - `http://localhost:3000/public-architectures/${params.id}/stars`, + `${process.env.NEXT_PUBLIC_BACK_URL}/public-architectures/${params.id}/stars`, { method: data!.stars.length > 0 ? 'DELETE' : 'POST', credentials: 'include', @@ -59,7 +59,7 @@ export default function ArchitectureDetailPage() { const handleImport = async () => { await fetch( - `http://localhost:3000/public-architectures/${params.id}/imports`, + `${process.env.NEXT_PUBLIC_BACK_URL}/public-architectures/${params.id}/imports`, { method: 'POST', credentials: 'include', @@ -115,7 +115,6 @@ export default function ArchitectureDetailPage() {
    -
    {JSON.stringify(data, null, 2)}
    ); diff --git a/apps/hub/src/app/my/architectures/page.tsx b/apps/hub/src/app/my/architectures/page.tsx index 00995c43..6e4cab34 100644 --- a/apps/hub/src/app/my/architectures/page.tsx +++ b/apps/hub/src/app/my/architectures/page.tsx @@ -6,7 +6,9 @@ import { Suspense } from 'react'; export default function MyArchitecturesPage() { return ( - + ); } diff --git a/apps/hub/src/app/my/shared/page.tsx b/apps/hub/src/app/my/shared/page.tsx index a5926b0f..59b45f4e 100644 --- a/apps/hub/src/app/my/shared/page.tsx +++ b/apps/hub/src/app/my/shared/page.tsx @@ -5,7 +5,9 @@ import { Suspense } from 'react'; export default function MySharedPage() { return ( - + ); } diff --git a/apps/hub/src/app/my/starred/page.tsx b/apps/hub/src/app/my/starred/page.tsx index 21700778..0bcc9961 100644 --- a/apps/hub/src/app/my/starred/page.tsx +++ b/apps/hub/src/app/my/starred/page.tsx @@ -5,7 +5,9 @@ import { Suspense } from 'react'; export default function MyStarredPage() { return ( - + ); } diff --git a/apps/hub/src/app/page.tsx b/apps/hub/src/app/page.tsx index 61c42ee4..2284fea7 100644 --- a/apps/hub/src/app/page.tsx +++ b/apps/hub/src/app/page.tsx @@ -5,7 +5,9 @@ import { Suspense } from 'react'; export default function Home() { return ( - + ); } diff --git a/apps/hub/src/components/GlobalHeader/index.tsx b/apps/hub/src/components/GlobalHeader/index.tsx index c95ac969..b53223cf 100644 --- a/apps/hub/src/components/GlobalHeader/index.tsx +++ b/apps/hub/src/components/GlobalHeader/index.tsx @@ -16,10 +16,13 @@ export const GlobalHeader = () => { }, []); const handleLogin = async () => { - const res = await fetch('http://localhost:3000/auth/login', { - method: 'POST', - credentials: 'include', - }); + const res = await fetch( + `${process.env.NEXT_PUBLIC_BACK_URL}/auth/login`, + { + method: 'POST', + credentials: 'include', + }, + ); if (res.ok) { setIsLoggedIn(true); localStorage.setItem('isLoggedIn', 'true'); @@ -29,7 +32,7 @@ export const GlobalHeader = () => { }; const handleLogout = () => { - fetch('http://localhost:3000/auth/logout', { + fetch(`${process.env.NEXT_PUBLIC_BACK_URL}/auth/logout`, { method: 'POST', credentials: 'include', }); From 248528f524a1b2ebdcd4c88c21302282e39c94be Mon Sep 17 00:00:00 2001 From: p1n9d3v Date: Wed, 27 Nov 2024 20:48:29 +0900 Subject: [PATCH 033/124] =?UTF-8?q?=E2=9C=A8=20Feat(client):=20server=20pr?= =?UTF-8?q?operties=20=EC=9E=85=EB=A0=A5=20UI=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/client/src/App.tsx | 3 +- .../components/NCloud/NetworksBar/index.tsx | 14 +- .../NCloud/PropertiesBar/ServerProperties.tsx | 134 ++++++++++++++++++ .../components/NCloud/PropertiesBar/index.tsx | 32 +++++ .../src/components/NCloud/PropertySelect.tsx | 42 ------ apps/client/src/contexts/NCloudContext.tsx | 2 +- apps/client/src/hooks/useNCloud.ts | 27 ++-- apps/client/src/models/ncloud/constants.ts | 32 +++++ apps/client/src/models/ncloud/index.ts | 3 +- apps/client/src/utils/index.ts | 11 ++ 10 files changed, 240 insertions(+), 60 deletions(-) create mode 100644 apps/client/src/components/NCloud/PropertiesBar/ServerProperties.tsx create mode 100644 apps/client/src/components/NCloud/PropertiesBar/index.tsx delete mode 100644 apps/client/src/components/NCloud/PropertySelect.tsx create mode 100644 apps/client/src/models/ncloud/constants.ts diff --git a/apps/client/src/App.tsx b/apps/client/src/App.tsx index d069c7ef..4058d78a 100644 --- a/apps/client/src/App.tsx +++ b/apps/client/src/App.tsx @@ -1,8 +1,8 @@ import CloudGraph from '@/src/CloudGraph'; -import ErrorBoundary from '@components/ErrorBoundary'; import Header from '@components/Layout/Header'; import Sidebar from '@components/Layout/Sidebar'; import NetworksBar from '@components/NCloud/NetworksBar/index'; +import PropertiesBar from '@components/NCloud/PropertiesBar'; import Box from '@mui/material/Box'; function App() { @@ -29,6 +29,7 @@ function App() { + ); } diff --git a/apps/client/src/components/NCloud/NetworksBar/index.tsx b/apps/client/src/components/NCloud/NetworksBar/index.tsx index 0f62b92b..f8ee6fa5 100644 --- a/apps/client/src/components/NCloud/NetworksBar/index.tsx +++ b/apps/client/src/components/NCloud/NetworksBar/index.tsx @@ -2,7 +2,12 @@ import RegionSelect from '@components/NCloud/NetworksBar/RegionSelect'; import SubnetSelect from '@components/NCloud/NetworksBar/SubnetSelect'; import VpcSelect from '@components/NCloud/NetworksBar/VpcSelect'; import useNCloud from '@hooks/useNCloud'; -import { AppBar, Divider, Stack, Toolbar, Typography } from '@mui/material'; +import AppBar from '@mui/material/AppBar'; +import Divider from '@mui/material/Divider'; +import Stack from '@mui/material/Stack'; +import Toolbar from '@mui/material/Toolbar'; +import Typography from '@mui/material/Typography'; +import ChevronRightIcon from '@mui/icons-material/ChevronRight'; export default () => { const { @@ -40,7 +45,6 @@ export default () => { paddingY: 2, }} > - {' '} { } + divider={} spacing={2} + sx={{ + display: 'flex', + alignItems: 'center', + }} > { + const { selectedResource, updateProperties } = useNCloud(); + + const [serverImageCode, setServerImageCode] = useState(''); + const [specCode, setSpecCode] = useState(''); + + const [name, setName] = useState(''); + + useEffect(() => { + if (!selectedResource) return; + const { properties } = selectedResource; + setName(properties.name || ''); + setServerImageCode(properties.server_image || ''); + setSpecCode(properties.server_product_code || ''); + }, [selectedResource]); + + const handleChangeImage = (e: SelectChangeEvent) => { + const code = e.target.value as string; + if (!selectedResource) return; + setServerImageCode(code); + const firstSpecCode = SERVER_IMAGE_SPEC_CODE[code][0]?.code; + + setSpecCode(firstSpecCode); + updateProperties(selectedResource.id, { + server_image: code, + server_product_code: firstSpecCode, + }); + }; + + const handleChangeSpec = (e: SelectChangeEvent) => { + const code = e.target.value as string; + if (!selectedResource) return; + setSpecCode(code); + updateProperties(selectedResource.id, { server_product_code: code }); + }; + + const handleChangeName = (e: React.ChangeEvent) => { + const newName = e.target.value; + setName(newName); + if (!selectedResource) return; + updateProperties(selectedResource.id, { name: newName }); + }; + + return ( + } + spacing={2} + > + + Resource Name + e.stopPropagation()} + onChange={handleChangeName} + /> + + + Server Image + + + + Spec + + + + ); +}; diff --git a/apps/client/src/components/NCloud/PropertiesBar/index.tsx b/apps/client/src/components/NCloud/PropertiesBar/index.tsx new file mode 100644 index 00000000..fefff6b7 --- /dev/null +++ b/apps/client/src/components/NCloud/PropertiesBar/index.tsx @@ -0,0 +1,32 @@ +import ServerProperties from '@components/NCloud/PropertiesBar/ServerProperties'; +import useNCloud from '@hooks/useNCloud'; +import { AppBar, Toolbar } from '@mui/material'; +export default () => { + const { selectedResource } = useNCloud(); + + return ( + + + + + + ); +}; diff --git a/apps/client/src/components/NCloud/PropertySelect.tsx b/apps/client/src/components/NCloud/PropertySelect.tsx deleted file mode 100644 index 0b347fbd..00000000 --- a/apps/client/src/components/NCloud/PropertySelect.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import * as React from 'react'; -import Box from '@mui/material/Box'; -import InputLabel from '@mui/material/InputLabel'; -import MenuItem from '@mui/material/MenuItem'; -import FormControl from '@mui/material/FormControl'; -import Select, { SelectChangeEvent } from '@mui/material/Select'; - -type Props = {}; - -const REGION_OPTIONS = [ - { value: 'kr', label: 'Korea' }, - { value: 'jp', label: 'Japan' }, -]; - -export default ({}: Props) => { - const [region, setRegion] = React.useState(''); - - const handleChange = (event: SelectChangeEvent) => { - setRegion(event.target.value as string); - }; - - return ( - - - Region - - - - ); -}; diff --git a/apps/client/src/contexts/NCloudContext.tsx b/apps/client/src/contexts/NCloudContext.tsx index 7f48df8a..655d096a 100644 --- a/apps/client/src/contexts/NCloudContext.tsx +++ b/apps/client/src/contexts/NCloudContext.tsx @@ -10,7 +10,7 @@ import { type SelectedResource = { id: string; type: string; - properties: {}; + properties: { [key: string]: string }; }; type NCloudContextProps = { diff --git a/apps/client/src/hooks/useNCloud.ts b/apps/client/src/hooks/useNCloud.ts index b8106742..c1efd0fe 100644 --- a/apps/client/src/hooks/useNCloud.ts +++ b/apps/client/src/hooks/useNCloud.ts @@ -14,6 +14,7 @@ import { useEffect } from 'react'; export default () => { const { selectedNodeId, selectedGroupId } = useSelection(); + //TODO: region,vpc 등 들고 있을 필요가 없음, 추후 수정 const { region, vpc, @@ -44,20 +45,21 @@ export default () => { } = useGraph(); useEffect(() => { - if (selectedNodeId) { - const node = nodes[selectedNodeId]; - if (!node) return; - setRegion(node.properties.region); - setVpc(node.properties.vpc); - setSubnet(node.properties.subnet); - setSelectedResource({ - id: selectedNodeId, - type: node.type, - properties: node.properties, - }); - } else { + if (!selectedNodeId || !nodes[selectedNodeId]) { setSelectedResource(undefined); + return; } + + const node = nodes[selectedNodeId]; + setRegion(node.properties.region); + setVpc(node.properties.vpc); + setSubnet(node.properties.subnet); + + setSelectedResource({ + id: selectedNodeId, + type: node.type, + properties: node.properties, + }); }, [selectedNodeId, nodes]); useEffect(() => { @@ -256,6 +258,7 @@ export default () => { selectedResource, updateVpc, addResource, + updateProperties, createRegion, removeVpc, removeSubnet, diff --git a/apps/client/src/models/ncloud/constants.ts b/apps/client/src/models/ncloud/constants.ts new file mode 100644 index 00000000..f005cb86 --- /dev/null +++ b/apps/client/src/models/ncloud/constants.ts @@ -0,0 +1,32 @@ +export const SERVER_OS_IMAGES = [ + { code: '19463675', name: 'rokcy-8.8-base', hyperVisor: 'KVM' }, + { code: '25624115', name: 'rocky-8.10-base', hyperVisor: 'XEN' }, + { code: '23221307', name: 'win-2022-en', hyperVisor: 'KVM' }, + { code: '23214590', name: 'ubuntu-22.04', hyperVisor: 'KVM' }, +]; + +export const SERVER_IMAGE_SPEC_CODE: { + [key: string]: { code: string; info: string }[]; +} = { + '25624115': [ + { code: 'c2-g2-s50', info: 'vCPU 2EA, Memory 4GB, [SSD]Disk 50GB' }, + { code: 'c4-g2-s50', info: 'vCPU 4EA, Memory 8GB, [SSD]Disk 50GB' }, + { code: 'c8-g2-s50', info: 'vCPU 8EA, Memory 16GB, [SSD]Disk 50GB' }, + { code: 'c16-g2-s50', info: 'vCPU 16EA, Memory 32GB, [SSD]Disk 50GB' }, + { code: 'c32-g2-s50', info: 'vCPU 32EA, Memory 64GB, [SSD]Disk 50GB' }, + { code: 'c2-g2-h50', info: 'vCPU 2EA, Memory 4GB, Disk 50GB' }, + { code: 'c4-g2-h50', info: 'vCPU 4EA, Memory 8GB, Disk 50GB' }, + { code: 'c8-g2-h50', info: 'vCPU 8EA, Memory 16GB, Disk 50GB' }, + { code: 'c16-g2-h50', info: 'vCPU 16EA, Memory 32GB, Disk 50GB' }, + { code: 'c32-g2-h50', info: 'vCPU 32EA, Memory 64GB, Disk 50GB' }, + ], + '19463675': [ + { code: 'ci2-g3', info: 'vCPU 2EA, Memory 4GB' }, + { code: 'ci4-g3', info: 'vCPU 4EA, Memory 8GB' }, + { code: 'ci8-g3', info: 'vCPU 8EA, Memory 16GB' }, + { code: 'ci16-g3', info: 'vCPU 16EA, Memory 32GB' }, + { code: 'ci32-g3', info: 'vCPU 32EA, Memory 64GB' }, + { code: 'ci48-g3', info: 'vCPU 48EA, Memory 96GB' }, + { code: 'ci64-g3', info: 'vCPU 64EA, Memory 128GB' }, + ], +}; diff --git a/apps/client/src/models/ncloud/index.ts b/apps/client/src/models/ncloud/index.ts index f808e498..8be4ebad 100644 --- a/apps/client/src/models/ncloud/index.ts +++ b/apps/client/src/models/ncloud/index.ts @@ -55,11 +55,12 @@ const Server: Node = { '3d': { width: 128, height: 111 }, }, properties: { + name: '', region: '', subnet: '', vpc: '', acg: '', - server_image_product_code: '', + server_image: '', server_product_code: '', }, }; diff --git a/apps/client/src/utils/index.ts b/apps/client/src/utils/index.ts index d894f785..3f7f2f69 100644 --- a/apps/client/src/utils/index.ts +++ b/apps/client/src/utils/index.ts @@ -151,3 +151,14 @@ export const getDistanceToSegment = ( const dy = p.y - yy; return Math.sqrt(dx * dx + dy * dy); }; + +const debounce = (func: (...args: any[]) => void, delay: number) => { + let timeout: ReturnType | null = null; + + return (...args: any[]) => { + if (timeout) clearTimeout(timeout); // 이전 타이머 제거 + timeout = setTimeout(() => { + func(...args); // 지정된 시간 후 함수 실행 + }, delay); + }; +}; From c1e815c06268609f18091c4c668138a1dbd961a7 Mon Sep 17 00:00:00 2001 From: p1n9d3v Date: Wed, 27 Nov 2024 21:48:09 +0900 Subject: [PATCH 034/124] =?UTF-8?q?=F0=9F=A4=96=20Refactor(client):=20serv?= =?UTF-8?q?er=20Node=20text=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/Node/ncloud/ServerNode.tsx | 64 +++++++++++++++---- 1 file changed, 53 insertions(+), 11 deletions(-) diff --git a/apps/client/src/components/Node/ncloud/ServerNode.tsx b/apps/client/src/components/Node/ncloud/ServerNode.tsx index 73cc3f8f..7fd35ff6 100644 --- a/apps/client/src/components/Node/ncloud/ServerNode.tsx +++ b/apps/client/src/components/Node/ncloud/ServerNode.tsx @@ -1,9 +1,20 @@ import { useDimensionContext } from '@contexts/DimensionContext'; import { Node } from '@types'; -type Props = {}; +type Props = Partial; + +const convertToIsoMatrix = (x: number, y: number) => { + const isoMatrix = new DOMMatrix() + .rotate(30) + .skewX(-30) + .scale(1, 0.8602) + .translate(x, y); + + return isoMatrix; // 결과 행렬 반환 +}; +const Node3D = ({ properties }: Props) => { + const matrix = convertToIsoMatrix(0, 0); -const Node3D = () => { return ( <> { d="M128 37v37l-64 37L0 74V37L64 0l64 37ZM2.054 38.185v34.63L64 108.627l61.946-35.812v-34.63L64 2.373 2.054 38.185Z" > - + + {/* */} + {/* */} + {/* */} + {/* */} + {/* */} + { fill="#ffffff" style={{ userSelect: 'none' }} > - M5 - {/* {node.properties.instanceType} */} + {properties?.server_product_code.split('-')[0].slice(0, 4)} ); }; -const Node2D = () => { +const Node2D = ({ properties }: Props) => { return ( <> @@ -72,25 +98,41 @@ const Node2D = () => { opacity=".05" > + {/* */} + {/* file_type_nginx */} + {/* */} + {/* */} + {/* */} - M5 + {properties?.server_product_code.split('-')[0].slice(0, 4)} ); }; -export default ({}: Partial) => { +export default ({ properties }: Partial) => { const { dimension } = useDimensionContext(); - return dimension === '2d' ? : ; + return dimension === '2d' ? ( + + ) : ( + + ); }; From d56a54b97d1a216615e98f8587dc61f6e9c9c46e Mon Sep 17 00:00:00 2001 From: p1n9d3v Date: Wed, 27 Nov 2024 21:50:15 +0900 Subject: [PATCH 035/124] =?UTF-8?q?=F0=9F=90=9E=20Fix(client):=202d=20\<\>?= =?UTF-8?q?=203d=20=EB=B3=80=ED=99=98=EC=8B=9C=20=EA=B9=9C=EB=B9=A1?= =?UTF-8?q?=EC=9E=84=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/client/src/CloudGraph.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/client/src/CloudGraph.tsx b/apps/client/src/CloudGraph.tsx index 6dc84614..2bf94889 100644 --- a/apps/client/src/CloudGraph.tsx +++ b/apps/client/src/CloudGraph.tsx @@ -12,7 +12,7 @@ import { useNodeContext } from '@contexts/NodeContext'; import useConnection from '@hooks/useConnection'; import useGraph from '@hooks/useGraph'; import useSelection from '@hooks/useSelection'; -import { useEffect } from 'react'; +import { useEffect, useLayoutEffect } from 'react'; export default () => { const { @@ -75,7 +75,7 @@ export default () => { }; }, []); - useEffect(() => { + useLayoutEffect(() => { if (dimension === prevDimension) return; updatedPointForDimension(); From 99621e1ec3ed87490459a90815c0cbfd0895b66c Mon Sep 17 00:00:00 2001 From: p1n9d3v Date: Thu, 28 Nov 2024 01:43:11 +0900 Subject: [PATCH 036/124] =?UTF-8?q?=F0=9F=A4=96=20Refactor(client):=20prop?= =?UTF-8?q?erties=20=ED=8F=AC=EB=A7=B7=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/Group/ncloud/RegionGroup.tsx | 14 +++++------ apps/client/src/constants/index.ts | 2 +- apps/client/src/models/ncloud/constants.ts | 25 +++++++++++++++++++ apps/client/src/types/index.ts | 6 ++--- 4 files changed, 35 insertions(+), 12 deletions(-) diff --git a/apps/client/src/components/Group/ncloud/RegionGroup.tsx b/apps/client/src/components/Group/ncloud/RegionGroup.tsx index 2e30ebeb..2d222447 100644 --- a/apps/client/src/components/Group/ncloud/RegionGroup.tsx +++ b/apps/client/src/components/Group/ncloud/RegionGroup.tsx @@ -9,7 +9,7 @@ interface Props extends Partial { bounds: Bounds; } -const Region3D = ({ bounds, name, color }: Props) => { +const Region3D = ({ bounds, properties, color }: Props) => { const topLeftGrid = screenToGrid2d({ x: 0, y: 0 }); const topRightGrid = screenToGrid2d({ x: bounds.width, y: 0 }); const bottomRightGrid = screenToGrid2d({ @@ -50,12 +50,12 @@ const Region3D = ({ bounds, name, color }: Props) => { strokeWidth="8" fill="none" > - + ); }; -const Region2D = ({ bounds, color, name }: Props) => { +const Region2D = ({ bounds, color, properties }: Props) => { const points = `0 0, 0 ${bounds.height}, ${bounds.width} ${bounds.height}, ${bounds.width} 0`; return ( @@ -66,18 +66,18 @@ const Region2D = ({ bounds, color, name }: Props) => { strokeWidth="8" fill="none" > - + ); }; -export default ({ bounds, name }: Pick) => { +export default ({ bounds, properties }: Omit) => { const { dimension } = useDimensionContext(); const color = useMemo(() => generateRandomRGB(), []); return dimension === '2d' ? ( - + ) : ( - + ); }; diff --git a/apps/client/src/constants/index.ts b/apps/client/src/constants/index.ts index 429e8f61..9fb3330d 100644 --- a/apps/client/src/constants/index.ts +++ b/apps/client/src/constants/index.ts @@ -38,5 +38,5 @@ export const NETWORKS_CATEGORIES = [ 'region', 'vpc', 'subnet', - 'security-group', + // 'security-group', ]; diff --git a/apps/client/src/models/ncloud/constants.ts b/apps/client/src/models/ncloud/constants.ts index f005cb86..e3c6d8d4 100644 --- a/apps/client/src/models/ncloud/constants.ts +++ b/apps/client/src/models/ncloud/constants.ts @@ -1,3 +1,5 @@ +import { nanoid } from 'nanoid'; + export const SERVER_OS_IMAGES = [ { code: '19463675', name: 'rokcy-8.8-base', hyperVisor: 'KVM' }, { code: '25624115', name: 'rocky-8.10-base', hyperVisor: 'XEN' }, @@ -30,3 +32,26 @@ export const SERVER_IMAGE_SPEC_CODE: { { code: 'ci64-g3', info: 'vCPU 64EA, Memory 128GB' }, ], }; + +const krRegionId = `kr-${nanoid()}`; +const jpRegionId = `jp-${nanoid()}`; +const sgRegionId = `sg-${nanoid()}`; +export const REGIONS: { [key: string]: any } = { + KR: { + id: krRegionId, + value: 'KR', + label: 'Korea', + }, + JP: { + id: jpRegionId, + value: 'JP', + label: 'Japan', + }, + SG: { + id: sgRegionId, + value: 'SG', + label: 'Singapore', + }, +}; + +export const DEFAULT_REGION = 'KR'; diff --git a/apps/client/src/types/index.ts b/apps/client/src/types/index.ts index 3988f6f3..e31a3952 100644 --- a/apps/client/src/types/index.ts +++ b/apps/client/src/types/index.ts @@ -13,13 +13,12 @@ export type Bounds = Point & Size; export type Node = { id: string; type: string; - name: string; point: Point; size: { '2d': Size; '3d': Size; }; - properties: { [key: string]: any }; + properties: { [id: string]: any }; connectors: { [key: string]: Point }; }; @@ -40,9 +39,8 @@ export type Edge = { export type Group = { id: string; type: string; - name: string; nodeIds: string[]; - properties: { [key: string]: any }; + properties: { [id: string]: any }; childGroupIds: string[]; parentGroupId: string; }; From 20f3a1c2e2daa5010a926fc7ef3e0fbfb5dc70de Mon Sep 17 00:00:00 2001 From: p1n9d3v Date: Thu, 28 Nov 2024 04:00:59 +0900 Subject: [PATCH 037/124] =?UTF-8?q?=F0=9F=A4=96=20Refactor(client):=20prop?= =?UTF-8?q?erties=20=ED=83=80=EC=9E=85=20=EB=B3=80=EA=B2=BD=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=9D=B8=ED=95=9C=20=EB=A6=AC=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/client/mocks.ts | 12 +- apps/client/package.json | 3 +- .../components/Group/ncloud/SubnetGroup.tsx | 14 +- .../src/components/Group/ncloud/VPCGroup.tsx | 14 +- .../src/components/Layout/Header/index.tsx | 28 +- .../Layout/Sidebar/ServiceInstance.tsx | 4 +- .../NCloud/NetworksBar/RegionSelect.tsx | 63 ++-- .../NCloud/NetworksBar/SubnetSelect.tsx | 61 ++-- .../NCloud/NetworksBar/VpcSelect.tsx | 90 +++--- .../components/NCloud/NetworksBar/index.tsx | 65 ++-- .../NCloud/PropertiesBar/ServerProperties.tsx | 10 +- .../src/components/Node/ncloud/ServerNode.tsx | 6 +- apps/client/src/contexts/NCloudContext.tsx | 76 ----- apps/client/src/hooks/useNCloud.ts | 284 +++++++++--------- apps/client/src/main.tsx | 4 +- apps/client/src/models/ncloud/index.ts | 24 +- apps/client/src/utils/index.ts | 7 + 17 files changed, 358 insertions(+), 407 deletions(-) delete mode 100644 apps/client/src/contexts/NCloudContext.tsx diff --git a/apps/client/mocks.ts b/apps/client/mocks.ts index a58dfd0e..1531e309 100644 --- a/apps/client/mocks.ts +++ b/apps/client/mocks.ts @@ -4,7 +4,6 @@ import { nanoid } from 'nanoid'; const CloudFunctionNode: Node = { id: `node-${nanoid()}`, type: 'cloud-function', - name: 'CloudFunction1', point: { x: 270, y: 270 }, size: { '2d': { width: 90, height: 90 }, @@ -20,7 +19,6 @@ const CloudFunctionNode: Node = { const ObjectStorageNode: Node = { id: `node-${nanoid()}`, type: 'object-storage', - name: 'ObjectStorage1', point: { x: 100, y: 0 }, size: { '2d': { width: 90, height: 90 }, @@ -36,7 +34,6 @@ const ObjectStorageNode: Node = { const MySQLDBNode: Node = { id: `node-${nanoid()}`, type: 'db-mysql', - name: 'MySQLDB1', point: { x: 0, y: 0 }, size: { '2d': { width: 90, height: 90 }, @@ -52,7 +49,6 @@ const MySQLDBNode: Node = { const ServerNode: Node = { id: `node-${nanoid()}`, type: 'server', - name: 'WebServer1', point: { x: 90, y: 90 }, size: { '2d': { width: 90, height: 90 }, @@ -69,7 +65,6 @@ const ServerNode: Node = { const ServerNode2: Node = { id: `node-${nanoid()}`, type: 'server', - name: 'WebServer2', point: { x: 90, y: 90 }, size: { '2d': { width: 90, height: 90 }, @@ -86,34 +81,35 @@ const ServerNode2: Node = { const SubnetGroup: Group = { id: 'subnet1', type: 'subnet', - name: 'Subnet-1', nodeIds: [ServerNode.id, MySQLDBNode.id, ObjectStorageNode.id], properties: { cidr: '', }, + childGroupIds: [], + parentGroupId: '', }; const VpcGroup: Group = { id: 'vpc1', type: 'vpc', - name: 'VPC-1', nodeIds: [CloudFunctionNode.id], properties: { cidr: '', }, childGroupIds: [SubnetGroup.id], + parentGroupId: '', }; const RegionGroup: Group = { id: 'seoul', type: 'region', - name: 'region', nodeIds: [], properties: { regionCode: 'KR-1', }, childGroupIds: [VpcGroup.id], + parentGroupId: '', }; const mockNodes = [ diff --git a/apps/client/package.json b/apps/client/package.json index 28f934bb..ebe1ff6f 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -18,7 +18,8 @@ "@mui/material": "^6.1.5", "nanoid": "^5.0.8", "react": "^18.3.1", - "react-dom": "^18.3.1" + "react-dom": "^18.3.1", + "terraform": "file:../../packages/terraform" }, "devDependencies": { "@types/react": "^18.3.11", diff --git a/apps/client/src/components/Group/ncloud/SubnetGroup.tsx b/apps/client/src/components/Group/ncloud/SubnetGroup.tsx index 4fa4a922..2d222447 100644 --- a/apps/client/src/components/Group/ncloud/SubnetGroup.tsx +++ b/apps/client/src/components/Group/ncloud/SubnetGroup.tsx @@ -9,7 +9,7 @@ interface Props extends Partial { bounds: Bounds; } -const Subnet3D = ({ bounds, name, color }: Props) => { +const Region3D = ({ bounds, properties, color }: Props) => { const topLeftGrid = screenToGrid2d({ x: 0, y: 0 }); const topRightGrid = screenToGrid2d({ x: bounds.width, y: 0 }); const bottomRightGrid = screenToGrid2d({ @@ -50,12 +50,12 @@ const Subnet3D = ({ bounds, name, color }: Props) => { strokeWidth="8" fill="none" > - + ); }; -const Subnet2D = ({ bounds, color, name }: Props) => { +const Region2D = ({ bounds, color, properties }: Props) => { const points = `0 0, 0 ${bounds.height}, ${bounds.width} ${bounds.height}, ${bounds.width} 0`; return ( @@ -66,18 +66,18 @@ const Subnet2D = ({ bounds, color, name }: Props) => { strokeWidth="8" fill="none" > - + ); }; -export default ({ bounds, name }: Pick) => { +export default ({ bounds, properties }: Omit) => { const { dimension } = useDimensionContext(); const color = useMemo(() => generateRandomRGB(), []); return dimension === '2d' ? ( - + ) : ( - + ); }; diff --git a/apps/client/src/components/Group/ncloud/VPCGroup.tsx b/apps/client/src/components/Group/ncloud/VPCGroup.tsx index d9e595d8..2d222447 100644 --- a/apps/client/src/components/Group/ncloud/VPCGroup.tsx +++ b/apps/client/src/components/Group/ncloud/VPCGroup.tsx @@ -9,7 +9,7 @@ interface Props extends Partial { bounds: Bounds; } -const VPC3D = ({ bounds, name, color }: Props) => { +const Region3D = ({ bounds, properties, color }: Props) => { const topLeftGrid = screenToGrid2d({ x: 0, y: 0 }); const topRightGrid = screenToGrid2d({ x: bounds.width, y: 0 }); const bottomRightGrid = screenToGrid2d({ @@ -50,12 +50,12 @@ const VPC3D = ({ bounds, name, color }: Props) => { strokeWidth="8" fill="none" > - + ); }; -const VPC2D = ({ bounds, color, name }: Props) => { +const Region2D = ({ bounds, color, properties }: Props) => { const points = `0 0, 0 ${bounds.height}, ${bounds.width} ${bounds.height}, ${bounds.width} 0`; return ( @@ -66,18 +66,18 @@ const VPC2D = ({ bounds, color, name }: Props) => { strokeWidth="8" fill="none" > - + ); }; -export default ({ bounds, name }: Pick) => { +export default ({ bounds, properties }: Omit) => { const { dimension } = useDimensionContext(); const color = useMemo(() => generateRandomRGB(), []); return dimension === '2d' ? ( - + ) : ( - + ); }; diff --git a/apps/client/src/components/Layout/Header/index.tsx b/apps/client/src/components/Layout/Header/index.tsx index c24a4d7a..52cbd280 100644 --- a/apps/client/src/components/Layout/Header/index.tsx +++ b/apps/client/src/components/Layout/Header/index.tsx @@ -1,9 +1,10 @@ import { useDimensionContext } from '@contexts/DimensionContext'; +import { useNodeContext } from '@contexts/NodeContext'; import DarkModeIcon from '@mui/icons-material/DarkMode'; import GitHubIcon from '@mui/icons-material/GitHub'; import LightModeIcon from '@mui/icons-material/LightMode'; import ManageHistoryIcon from '@mui/icons-material/ManageHistory'; -import { ToggleButton, ToggleButtonGroup } from '@mui/material'; +import { Button, ToggleButton, ToggleButtonGroup } from '@mui/material'; import Box from '@mui/material/Box'; import ButtonGroup from '@mui/material/ButtonGroup'; import Divider from '@mui/material/Divider'; @@ -11,6 +12,7 @@ import IconButton from '@mui/material/IconButton'; import Stack from '@mui/material/Stack'; import { styled, useColorScheme } from '@mui/material/styles'; import Typography from '@mui/material/Typography'; +import { TerraformConvertor } from 'node_modules/terraform/convertor/TerraformConvertor'; const StyledBox = styled(Box)(({ theme }) => ({ display: 'flex', @@ -39,6 +41,29 @@ export default () => { const { mode: themeMode, setMode: setThemeMode } = useColorScheme(); const { dimension, toggleDimension } = useDimensionContext(); + const { + state: { nodes }, + } = useNodeContext(); + + const Converter = new TerraformConvertor(); + + const validateProperties = (node: any) => { + const { properties } = node; + const isValid = Object.values(properties).every((value) => { + return value !== ''; + }); + + return isValid; + }; + // if (Object.values(nodes).length > 0) { + // const node = Object.values(nodes)[0]; + // console.log(node); + // if (validateProperties(node)) { + // Converter.addResourceFromJson(node); + // console.log(Converter.generate()); + // } + // } + const handleToggleTheme = () => setThemeMode(themeMode === 'dark' ? 'light' : 'dark'); @@ -52,6 +77,7 @@ export default () => { + { - const { addResource } = useNCloud(); + const { createResource } = useNCloud(); return ( - addResource(type)}> + createResource(type)}> ); diff --git a/apps/client/src/components/NCloud/NetworksBar/RegionSelect.tsx b/apps/client/src/components/NCloud/NetworksBar/RegionSelect.tsx index 390fcfe1..42caac96 100644 --- a/apps/client/src/components/NCloud/NetworksBar/RegionSelect.tsx +++ b/apps/client/src/components/NCloud/NetworksBar/RegionSelect.tsx @@ -1,36 +1,21 @@ -import { Region } from '@types'; -import { Regions } from '@/src/models/ncloud'; -import useNCloud from '@hooks/useNCloud'; -import { ListItemIcon, Typography } from '@mui/material'; +import { REGIONS } from '@/src/models/ncloud/constants'; +import { ListItemIcon, Tooltip, Typography } from '@mui/material'; import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; import List from '@mui/material/List'; import ListItemButton from '@mui/material/ListItemButton'; import ListItemText from '@mui/material/ListItemText'; import Popover from '@mui/material/Popover'; +import { Region } from '@types'; import { useState } from 'react'; -const REGION_OPTIONS = [ - { - value: 'kr', - label: 'Korea', - }, - { - value: 'jp', - label: 'Japan', - }, - { - value: 'sg', - label: 'Singapore', - }, -]; - type Props = { - region: string; - onUpdateRegion: (region: Region) => void; + region: { [key: string]: any }; + disabled?: boolean; + onChangeRegion: (id: string, region: Region) => void; }; -export default ({ region, onUpdateRegion }: Props) => { +export default ({ region, disabled = false, onChangeRegion }: Props) => { const [anchorEl, setAnchorEl] = useState(null); const handlePopoverOpen = (event: React.MouseEvent) => { @@ -41,8 +26,10 @@ export default ({ region, onUpdateRegion }: Props) => { setAnchorEl(null); }; - const handleListItemClick = (value: Region) => { - onUpdateRegion(value); + const handleListItemClick = (id: string, value: Region) => { + if (region.id !== id) { + onChangeRegion(id, value); + } setAnchorEl(null); }; @@ -61,9 +48,22 @@ export default ({ region, onUpdateRegion }: Props) => { > Region - + + + + + + { }} > - {REGION_OPTIONS.map((option) => ( + {Object.values(REGIONS).map((option) => ( - handleListItemClick(option.value as Region) + handleListItemClick( + option.id, + option.value as Region, + ) } > diff --git a/apps/client/src/components/NCloud/NetworksBar/SubnetSelect.tsx b/apps/client/src/components/NCloud/NetworksBar/SubnetSelect.tsx index df21b793..be0c913b 100644 --- a/apps/client/src/components/NCloud/NetworksBar/SubnetSelect.tsx +++ b/apps/client/src/components/NCloud/NetworksBar/SubnetSelect.tsx @@ -12,13 +12,14 @@ import Popover from '@mui/material/Popover'; import Typography from '@mui/material/Typography'; import { useTheme } from '@mui/material/styles'; import { useState } from 'react'; +import { nanoid } from 'nanoid'; +import { Tooltip } from '@mui/material'; type Props = { - subnet: string; + subnet: { [id: string]: string } | undefined; subnetList: { [id: string]: string }; disabled?: boolean; - disabledRemove?: boolean; - onUpdateSubnet: (subnet: string) => void; + onChangeSubnet: (id: string, newSubnet: string) => void; onRemoveSubnet: (subnet: string) => void; }; @@ -26,8 +27,7 @@ export default ({ subnet, subnetList, disabled = false, - disabledRemove = false, - onUpdateSubnet, + onChangeSubnet, onRemoveSubnet, }: Props) => { const theme = useTheme(); @@ -41,22 +41,24 @@ export default ({ setAnchorEl(null); }; - const handleListItemClick = (value: string) => { - onUpdateSubnet(value); + const handleListItemClick = (id: string, value: string) => { + if (subnet?.id !== id) { + onChangeSubnet(id, value); + } setAnchorEl(null); }; - const handleAddVPC = (e: React.FormEvent) => { + const handleAddSubnet = (e: React.FormEvent) => { e.preventDefault(); - const vpc = e.currentTarget.vpc.value; - if (vpc) { - onUpdateSubnet(vpc); + const newSubnet = e.currentTarget.subnet.value; + if (newSubnet) { + onChangeSubnet(`subnet-${nanoid()}`, newSubnet); } setAnchorEl(null); }; const open = Boolean(anchorEl); - const id = open ? 'vpc' : undefined; + const id = open ? 'subnet' : undefined; return ( @@ -70,15 +72,22 @@ export default ({ > Subnet - + + + + -
    + e.stopPropagation()} @@ -114,12 +123,14 @@ export default ({ {Object.entries(subnetList).map(([id, value]) => ( handleListItemClick(value as string)} + onClick={() => + handleListItemClick(id, value as string) + } secondaryAction={ onRemoveSubnet(id)} > @@ -127,7 +138,7 @@ export default ({ } style={{ backgroundColor: - subnet === value + subnet?.id === id ? theme.palette.action.selected : undefined, }} diff --git a/apps/client/src/components/NCloud/NetworksBar/VpcSelect.tsx b/apps/client/src/components/NCloud/NetworksBar/VpcSelect.tsx index 05d9db71..a352bda7 100644 --- a/apps/client/src/components/NCloud/NetworksBar/VpcSelect.tsx +++ b/apps/client/src/components/NCloud/NetworksBar/VpcSelect.tsx @@ -13,13 +13,14 @@ import Popover from '@mui/material/Popover'; import Typography from '@mui/material/Typography'; import { useState } from 'react'; import { Tooltip } from '@mui/material'; +import { nanoid } from 'nanoid'; +import { findKeyByValue } from '@utils'; type Props = { - vpc: string; + vpc: { [id: string]: string } | undefined; vpcList: { [id: string]: string }; disabled?: boolean; - disabledRemove?: boolean; - onUpdateVpc: (vpc: string) => void; + onChangeVpc: (id: string, newVpc: string) => void; onRemoveVpc: (vpc: string) => void; }; @@ -27,8 +28,7 @@ export default ({ vpc, vpcList, disabled = false, - disabledRemove = false, - onUpdateVpc, + onChangeVpc, onRemoveVpc, }: Props) => { const theme = useTheme(); @@ -42,16 +42,20 @@ export default ({ setAnchorEl(null); }; - const handleListItemClick = (value: string) => { - onUpdateVpc(value); + const handleListItemClick = (id: string, value: string) => { + if (vpc?.id !== id) { + onChangeVpc(id, value); + } setAnchorEl(null); }; - const handleAddVPC = (e: React.FormEvent) => { + //TODO: 숫자 validation + const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); - const vpc = e.currentTarget.vpc.value; - if (vpc) { - onUpdateVpc(vpc); + const newVpc = e.currentTarget.vpc.value; + if (newVpc) { + const id = findKeyByValue(newVpc, vpcList) ?? `vpc-${nanoid()}`; + onChangeVpc(id, newVpc); } setAnchorEl(null); }; @@ -61,7 +65,7 @@ export default ({ return ( - + VPC - - + + + + + - + e.stopPropagation()} endAdornment={ - + } @@ -115,27 +128,24 @@ export default ({ {Object.entries(vpcList).map(([id, value]) => ( handleListItemClick(value as string)} + onClick={() => + handleListItemClick(id, value as string) + } secondaryAction={ - - - onRemoveVpc(id)} - > - - - - + + onRemoveVpc(id)} + > + + + } style={{ backgroundColor: - vpc === value + vpc?.id === id ? theme.palette.action.selected : undefined, }} diff --git a/apps/client/src/components/NCloud/NetworksBar/index.tsx b/apps/client/src/components/NCloud/NetworksBar/index.tsx index f8ee6fa5..b8c605e1 100644 --- a/apps/client/src/components/NCloud/NetworksBar/index.tsx +++ b/apps/client/src/components/NCloud/NetworksBar/index.tsx @@ -8,22 +8,23 @@ import Stack from '@mui/material/Stack'; import Toolbar from '@mui/material/Toolbar'; import Typography from '@mui/material/Typography'; import ChevronRightIcon from '@mui/icons-material/ChevronRight'; +import { REGIONS } from '@/src/models/ncloud/constants'; export default () => { const { selectedResource, - region, - subnet, - subnetList, - vpc, vpcList, - updateVpc, - updateRegion, - updateSubnet, + subnetList, + changeVpc, + changeSubnet, removeVpc, removeSubnet, + changeRegion, } = useNCloud(); + if (!selectedResource) return; + const { properties } = selectedResource; + return ( { whiteSpace: 'nowrap', }} > - {selectedResource?.type.toUpperCase()} + {selectedResource.type.toUpperCase()} { }} > - {selectedResource && - Object.hasOwn(selectedResource?.properties, 'vpc') && ( - - )} - {selectedResource && - Object.hasOwn( - selectedResource?.properties, - 'subnet', - ) && ( - - )} + {Object.hasOwn(properties, 'vpc') && properties.region && ( + + )} + {Object.hasOwn(properties, 'subnet') && properties.vpc && ( + + )} diff --git a/apps/client/src/components/NCloud/PropertiesBar/ServerProperties.tsx b/apps/client/src/components/NCloud/PropertiesBar/ServerProperties.tsx index 5ddfa352..31fa5243 100644 --- a/apps/client/src/components/NCloud/PropertiesBar/ServerProperties.tsx +++ b/apps/client/src/components/NCloud/PropertiesBar/ServerProperties.tsx @@ -26,8 +26,8 @@ export default ({}: Props) => { if (!selectedResource) return; const { properties } = selectedResource; setName(properties.name || ''); - setServerImageCode(properties.server_image || ''); - setSpecCode(properties.server_product_code || ''); + setServerImageCode(properties.server_image_number || ''); + setSpecCode(properties.server_spec_code || ''); }, [selectedResource]); const handleChangeImage = (e: SelectChangeEvent) => { @@ -38,8 +38,8 @@ export default ({}: Props) => { setSpecCode(firstSpecCode); updateProperties(selectedResource.id, { - server_image: code, - server_product_code: firstSpecCode, + server_image_number: code.toLowerCase(), + server_spec_code: firstSpecCode.toLowerCase(), }); }; @@ -47,7 +47,7 @@ export default ({}: Props) => { const code = e.target.value as string; if (!selectedResource) return; setSpecCode(code); - updateProperties(selectedResource.id, { server_product_code: code }); + updateProperties(selectedResource.id, { server_spec_code: code }); }; const handleChangeName = (e: React.ChangeEvent) => { diff --git a/apps/client/src/components/Node/ncloud/ServerNode.tsx b/apps/client/src/components/Node/ncloud/ServerNode.tsx index 7fd35ff6..db871f9f 100644 --- a/apps/client/src/components/Node/ncloud/ServerNode.tsx +++ b/apps/client/src/components/Node/ncloud/ServerNode.tsx @@ -73,7 +73,7 @@ const Node3D = ({ properties }: Props) => { fill="#ffffff" style={{ userSelect: 'none' }} > - {properties?.server_product_code.split('-')[0].slice(0, 4)} + {properties?.server_spec_code?.split('-')[0].slice(0, 4)} @@ -121,7 +121,9 @@ const Node2D = ({ properties }: Props) => { fill="#d86613" style={{ userSelect: 'none' }} > - {properties?.server_product_code.split('-')[0].slice(0, 4)} + {properties?.server_spec_code?.value + .split('-')[0] + .slice(0, 4)} diff --git a/apps/client/src/contexts/NCloudContext.tsx b/apps/client/src/contexts/NCloudContext.tsx deleted file mode 100644 index 655d096a..00000000 --- a/apps/client/src/contexts/NCloudContext.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { - createContext, - Dispatch, - PropsWithChildren, - SetStateAction, - useContext, - useState, -} from 'react'; - -type SelectedResource = { - id: string; - type: string; - properties: { [key: string]: string }; -}; - -type NCloudContextProps = { - region: string; - vpc: string; - vpcList: { [id: string]: string }; - subnet: string; - subnetList: { [id: string]: string }; - selectedResource: SelectedResource | undefined; - setSelectedResource: Dispatch>; - setRegion: Dispatch>; - setVpc: Dispatch>; - setVpcList: Dispatch>; - setSubnet: Dispatch>; - setSubnetList: Dispatch>; -}; - -const NCloudContext = createContext(undefined); - -export const NCloudProvider = ({ children }: PropsWithChildren) => { - const [region, setRegion] = useState('kr'); - const [vpc, setVpc] = useState(''); - const [vpcList, setVpcList] = useState<{ - [id: string]: string; - }>({}); - const [subnet, setSubnet] = useState(''); - const [subnetList, setSubnetList] = useState<{ - [id: string]: string; - }>({}); - - const [selectedResource, setSelectedResource] = useState< - SelectedResource | undefined - >(undefined); - - return ( - - {children} - - ); -}; - -export const useNCloudContext = () => { - const context = useContext(NCloudContext); - if (!context) { - throw new Error('NCloudContext: context is undefined'); - } - return context; -}; diff --git a/apps/client/src/hooks/useNCloud.ts b/apps/client/src/hooks/useNCloud.ts index c1efd0fe..6a5f0d35 100644 --- a/apps/client/src/hooks/useNCloud.ts +++ b/apps/client/src/hooks/useNCloud.ts @@ -1,34 +1,26 @@ -import { - NcloudGroupFactory, - NcloudNodeFactory, - Regions, -} from '@/src/models/ncloud'; -import { NETWORKS_CATEGORIES } from '@constants'; -import { useNCloudContext } from '@contexts/NCloudContext'; +import { NcloudGroupFactory, NcloudNodeFactory } from '@/src/models/ncloud'; +import { DEFAULT_REGION, REGIONS } from '@/src/models/ncloud/constants'; import { getInitPoint } from '@helpers/cloud'; import useGraph from '@hooks/useGraph'; import useSelection from '@hooks/useSelection'; -import { Node, Region } from '@types'; +import { Region } from '@types'; +import { findKeyByValue } from '@utils'; import { nanoid } from 'nanoid'; -import { useEffect } from 'react'; +import { useEffect, useState } from 'react'; export default () => { const { selectedNodeId, selectedGroupId } = useSelection(); - //TODO: region,vpc 등 들고 있을 필요가 없음, 추후 수정 - const { - region, - vpc, - selectedResource, - vpcList, - subnet, - subnetList, - setRegion, - setVpc, - setSubnet, - setSubnetList, - setVpcList, - setSelectedResource, - } = useNCloudContext(); + const [selectedResource, setSelectedResource] = useState< + | { + id: string; + type: string; + properties: { [key: string]: any }; + } + | undefined + >(undefined); + + const [vpcList, setVpcList] = useState<{ [id: string]: string }>({}); + const [subnetList, setSubnetList] = useState<{ [id: string]: string }>({}); const { nodes, @@ -51,9 +43,6 @@ export default () => { } const node = nodes[selectedNodeId]; - setRegion(node.properties.region); - setVpc(node.properties.vpc); - setSubnet(node.properties.subnet); setSelectedResource({ id: selectedNodeId, @@ -63,179 +52,177 @@ export default () => { }, [selectedNodeId, nodes]); useEffect(() => { - const regionGroup = groups[region]; + if (!selectedNodeId) return; + const node = nodes[selectedNodeId]; + const regionGroup = groups[node.properties.region?.id]; if (!regionGroup) return; setVpcList({ ...Object.fromEntries( - regionGroup.childGroupIds.map((id) => [id, groups[id].name]), + regionGroup.childGroupIds.map((id) => [ + id, + groups[id].properties.name, + ]), ), }); - const vpcGroup = groups[vpc]; + const vpcGroup = groups[node.properties.vpc?.id]; if (!vpcGroup) return; setSubnetList({ ...Object.fromEntries( - vpcGroup.childGroupIds.map((id) => [id, groups[id].name]), + vpcGroup.childGroupIds.map((id) => [ + id, + groups[id].properties.name, + ]), ), }); - }, [region, groups]); + }, [selectedNodeId, nodes]); - const addResource = (type: string) => { + const createResource = (type: string) => { if (!svgRef.current) return; const node = NcloudNodeFactory(type); const id = `node-${nanoid()}`; + const region = REGIONS[DEFAULT_REGION]; addNode({ ...node, id, properties: { ...node.properties, - region, + region: { + id: region.id, + value: region.value, + }, }, point: getInitPoint(svgRef.current!), }); - if (!isExistGroup(region)) { - createRegion(region, id); - } else { - addNodeToGroup(region, id); + const regionId = REGIONS[DEFAULT_REGION].id; + if (!isExistGroup(regionId)) { + createRegion(regionId, region.value); } + addNodeToGroup(regionId, id); }; - const createRegion = (region: string, nodeId?: string) => { + const createRegion = (id: string, region: string) => { addGroup({ ...NcloudGroupFactory('region'), - id: region, - name: Regions[region].toUpperCase(), - nodeIds: nodeId ? [nodeId] : [], + id, + nodeIds: [], + properties: { + name: REGIONS[region].label, + }, }); }; - const createVpc = (vpc: string, nodeId?: string) => { - addGroup({ - ...NcloudGroupFactory('vpc'), - id: vpc, - name: vpc, - nodeIds: nodeId ? [nodeId] : [], - }); - }; + const changeRegion = (id: string, newRegion: Region) => { + if (!selectedNodeId) return; + if (!isExistGroup(id)) { + createRegion(id, newRegion); + } - const removeNodeRelatedGroup = (node: Node, groupCategories: string[]) => { - const properties = node.properties; - const relatedGroupIds = groupCategories - .map((type) => properties[type]) - .filter(Boolean); + addNodeToGroup(id, selectedNodeId); + const node = nodes[selectedNodeId]; + const { properties } = node; + if (properties.region) { + removeNodeFromGroup(properties.region.id, selectedNodeId); + } - relatedGroupIds.forEach((groupId) => - removeNodeFromGroup(groupId, node.id), - ); + updateProperties(selectedNodeId, { + region: { + id, + value: REGIONS[newRegion].value, + }, + }); }; - const updateRegion = (newRegion: Region) => { - if (selectedNodeId && region !== newRegion) { - if (isExistGroup(newRegion)) { - addNodeToGroup(newRegion, selectedNodeId); - } else { - createRegion(newRegion, selectedNodeId); - } - - removeNodeRelatedGroup(nodes[selectedNodeId], NETWORKS_CATEGORIES); - - const updatedProperties = NETWORKS_CATEGORIES.reduce((acc, cur) => { - return { - ...acc, - [cur]: '', - }; - }, {}); - - updateProperties(selectedNodeId, { - ...updatedProperties, - region: newRegion, - }); - } + const createVpc = (id: string, newVpc: string) => { + addGroup({ + ...NcloudGroupFactory('vpc'), + id, + nodeIds: [], + properties: { + name: newVpc, + }, + }); }; - const updateVpc = (newVpc: string) => { - if (selectedNodeId && vpc !== newVpc) { - if (!isExistGroup(newVpc)) { - createVpc(newVpc); - } - - addNodeToGroup(newVpc, selectedNodeId); - addChildGroup(newVpc, region, selectedNodeId); - const node = nodes[selectedNodeId]; - removeNodeRelatedGroup(node, NETWORKS_CATEGORIES); + const changeVpc = (id: string, newVpc: string) => { + if (!selectedNodeId) return; + const node = nodes[selectedNodeId]; + const prevVpcId = findKeyByValue(newVpc, vpcList); + if (!prevVpcId) { + createVpc(id, newVpc); + } - const updatedProperties = NETWORKS_CATEGORIES.reduce((acc, cur) => { - return { - ...acc, - [cur]: '', - }; - }, {}); + const idToUpdate = prevVpcId ?? id; + const { properties } = node; + addNodeToGroup(idToUpdate, selectedNodeId); + addChildGroup(idToUpdate, properties.region.id, selectedNodeId); - updateProperties(selectedNodeId, { - ...updatedProperties, - region, - vpc: newVpc, - }); + if (properties.vpc) { + removeNodeFromGroup(properties.vpc.id, selectedNodeId); } + + updateProperties(selectedNodeId, { + vpc: { + id: idToUpdate, + value: newVpc, + }, + }); }; - const createSubnet = (subnet: string, nodeId?: string) => { + const createSubnet = (id: string, newSubnet: string) => { addGroup({ ...NcloudGroupFactory('subnet'), - id: subnet, - name: subnet, - nodeIds: nodeId ? [nodeId] : [], + id, + nodeIds: [], + properties: { + name: newSubnet, + }, }); }; - const updateSubnet = (newSubnet: string) => { - if (selectedNodeId && subnet !== newSubnet) { - if (!isExistGroup(newSubnet)) { - createSubnet(newSubnet); - } - - addNodeToGroup(newSubnet, selectedNodeId); - if (vpc || region) { - addChildGroup(newSubnet, vpc || region, selectedNodeId); - } - const node = nodes[selectedNodeId]; - removeNodeRelatedGroup(node, NETWORKS_CATEGORIES); + const changeSubnet = (id: string, newSubnet: string) => { + if (!selectedNodeId) return; + const node = nodes[selectedNodeId]; + const prevSubnetId = findKeyByValue(newSubnet, subnetList); + if (!prevSubnetId) { + createSubnet(id, newSubnet); + } - const updatedProperties = NETWORKS_CATEGORIES.reduce((acc, cur) => { - return { - ...acc, - [cur]: '', - }; - }, {}); + const idToUpdate = prevSubnetId ?? id; + const { properties } = node; + addNodeToGroup(idToUpdate, selectedNodeId); + addChildGroup(idToUpdate, properties.vpc.id, selectedNodeId); - updateProperties(selectedNodeId, { - ...updatedProperties, - region, - vpc, - subnet: newSubnet, - }); + if (properties.subnet) { + removeNodeFromGroup(properties.subnet.id, selectedNodeId); } + + updateProperties(selectedNodeId, { + subnet: { + id: idToUpdate, + value: newSubnet, + }, + }); }; - const removeVpc = (vpc: string) => { - if (selectedNodeId) { - excludeNodeFromGroup(vpc, selectedNodeId); - updateProperties(selectedNodeId, { - ...nodes[selectedNodeId].properties, - vpc: '', - }); - } + const removeVpc = (vpcId: string) => { + if (!selectedNodeId) return; + excludeNodeFromGroup(vpcId, selectedNodeId); + updateProperties(selectedNodeId, { + ...nodes[selectedNodeId].properties, + vpc: undefined, + }); }; - const removeSubnet = (subnet: string) => { + const removeSubnet = (subnetId: string) => { if (selectedNodeId) { - excludeNodeFromGroup(subnet, selectedNodeId); + excludeNodeFromGroup(subnetId, selectedNodeId); updateProperties(selectedNodeId, { ...nodes[selectedNodeId].properties, - subnet: '', + subnet: undefined, }); } }; @@ -250,20 +237,17 @@ export default () => { }; return { - region, - vpc, - vpcList, - subnet, subnetList, + vpcList, selectedResource, - updateVpc, - addResource, - updateProperties, + createResource, createRegion, + createVpc, + changeVpc, + changeSubnet, + updateProperties, removeVpc, removeSubnet, - updateRegion, - createSubnet, - updateSubnet, + changeRegion, }; }; diff --git a/apps/client/src/main.tsx b/apps/client/src/main.tsx index 5e0f537f..340298e7 100644 --- a/apps/client/src/main.tsx +++ b/apps/client/src/main.tsx @@ -23,9 +23,7 @@ createRoot(document.getElementById('root')!).render( - - - + diff --git a/apps/client/src/models/ncloud/index.ts b/apps/client/src/models/ncloud/index.ts index 8be4ebad..3647eadd 100644 --- a/apps/client/src/models/ncloud/index.ts +++ b/apps/client/src/models/ncloud/index.ts @@ -31,7 +31,6 @@ export const NcloudGroupFactory = (type: string) => { const GraphNodeProperties = { id: '', - name: '', type: '', point: { x: 0, y: 0 }, connectors: {}, @@ -39,7 +38,6 @@ const GraphNodeProperties = { const GraphGroupProperties = { id: '', - name: '', type: '', nodeIds: [], properties: {}, @@ -55,13 +53,12 @@ const Server: Node = { '3d': { width: 128, height: 111 }, }, properties: { - name: '', - region: '', - subnet: '', - vpc: '', - acg: '', - server_image: '', - server_product_code: '', + name: undefined, + region: undefined, + subnet: undefined, + vpc: undefined, + server_image_number: undefined, + server_spec_code: undefined, }, }; @@ -96,6 +93,9 @@ const MySQLDB: Node = { const Region: Group = { ...GraphGroupProperties, type: 'region', + properties: { + name: undefined, + }, }; const Vpc: Group = { @@ -107,9 +107,3 @@ const Subnet: Group = { ...GraphGroupProperties, type: 'subnet', }; - -export const Regions: { [key: string]: string } = { - kr: 'korea', - jp: 'japan', - sg: 'singapore', -}; diff --git a/apps/client/src/utils/index.ts b/apps/client/src/utils/index.ts index 3f7f2f69..e0a1638e 100644 --- a/apps/client/src/utils/index.ts +++ b/apps/client/src/utils/index.ts @@ -162,3 +162,10 @@ const debounce = (func: (...args: any[]) => void, delay: number) => { }, delay); }; }; + +export const findKeyByValue = ( + value: string, + list: { [id: string]: string }, +) => { + return Object.keys(list).find((key) => list[key] === value); +}; From a3cee5bd35eec9f98d6459037fd5b655638a4b0c Mon Sep 17 00:00:00 2001 From: p1n9d3v Date: Thu, 28 Nov 2024 10:25:48 +0900 Subject: [PATCH 038/124] =?UTF-8?q?=E2=9C=A8=20Feat(client):=20terraform?= =?UTF-8?q?=20code=20converter=20=EC=97=B0=EA=B2=B0=EB=B0=8F=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=EB=B7=B0=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/client/src/components/CodeDrawer.tsx | 91 +++++++++++++ .../src/components/Layout/Header/index.tsx | 128 +++++++++++------- .../NCloud/PropertiesBar/ServerProperties.tsx | 17 ++- .../src/components/Node/ncloud/ServerNode.tsx | 4 +- apps/client/src/helpers/group.ts | 9 ++ apps/client/src/helpers/node.ts | 7 + .../client/src/models/ncloud/CloudFunction.ts | 21 +++ apps/client/src/models/ncloud/MySQLDB.ts | 21 +++ apps/client/src/models/ncloud/Networks.ts | 44 ++++++ apps/client/src/models/ncloud/Server.ts | 32 +++++ apps/client/src/models/ncloud/index.ts | 96 ++----------- apps/client/src/models/ncloud/utils.ts | 31 +++++ 12 files changed, 353 insertions(+), 148 deletions(-) create mode 100644 apps/client/src/components/CodeDrawer.tsx create mode 100644 apps/client/src/models/ncloud/CloudFunction.ts create mode 100644 apps/client/src/models/ncloud/MySQLDB.ts create mode 100644 apps/client/src/models/ncloud/Networks.ts create mode 100644 apps/client/src/models/ncloud/Server.ts create mode 100644 apps/client/src/models/ncloud/utils.ts diff --git a/apps/client/src/components/CodeDrawer.tsx b/apps/client/src/components/CodeDrawer.tsx new file mode 100644 index 00000000..1e6b37db --- /dev/null +++ b/apps/client/src/components/CodeDrawer.tsx @@ -0,0 +1,91 @@ +import CloseIcon from '@mui/icons-material/Close'; +import ContentCopyIcon from '@mui/icons-material/ContentCopy'; +import { + Box, + Button, + Drawer, + IconButton, + Typography, + useTheme, +} from '@mui/material'; +import { useState } from 'react'; +import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; +import { materialDark } from 'react-syntax-highlighter/dist/esm/styles/prism'; +import { TypeAnimation } from 'react-type-animation'; + +type Props = { + code: string; + open: boolean; + onClose: () => void; +}; + +export default ({ code, open, onClose }: Props) => { + const theme = useTheme(); + const [copied, setCopied] = useState(false); + const [isFinished, setIsFinished] = useState(false); + + const handleCopy = () => { + navigator.clipboard.writeText(code); + setCopied(true); + setTimeout(() => setCopied(false), 2000); // 2초 후 복사 상태 리셋 + }; + + return ( + + + + Terraform Code Viewer + + + + + + {!isFinished ? ( + + setIsFinished(true)]} + speed={80} + cursor={true} + style={{ fontSize: '14px', lineHeight: '1.5' }} + repeat={0} + /> + + ) : ( + + {code} + + )} + + + + + + ); +}; diff --git a/apps/client/src/components/Layout/Header/index.tsx b/apps/client/src/components/Layout/Header/index.tsx index 52cbd280..e97a5d1a 100644 --- a/apps/client/src/components/Layout/Header/index.tsx +++ b/apps/client/src/components/Layout/Header/index.tsx @@ -1,3 +1,6 @@ +import { ServerRequiredFields } from '@/src/models/ncloud/Server'; +import { transformObject, validateObject } from '@/src/models/ncloud/utils'; +import CodeDrawer from '@components/CodeDrawer'; import { useDimensionContext } from '@contexts/DimensionContext'; import { useNodeContext } from '@contexts/NodeContext'; import DarkModeIcon from '@mui/icons-material/DarkMode'; @@ -13,6 +16,7 @@ import Stack from '@mui/material/Stack'; import { styled, useColorScheme } from '@mui/material/styles'; import Typography from '@mui/material/Typography'; import { TerraformConvertor } from 'node_modules/terraform/convertor/TerraformConvertor'; +import { useState } from 'react'; const StyledBox = styled(Box)(({ theme }) => ({ display: 'flex', @@ -40,6 +44,8 @@ const StyledIconButton = styled(IconButton)(({ theme }) => ({ export default () => { const { mode: themeMode, setMode: setThemeMode } = useColorScheme(); const { dimension, toggleDimension } = useDimensionContext(); + const [openDrawer, setOpenDrawer] = useState(false); + const [terraformCode, setTerraformCode] = useState(''); const { state: { nodes }, @@ -47,22 +53,21 @@ export default () => { const Converter = new TerraformConvertor(); - const validateProperties = (node: any) => { - const { properties } = node; - const isValid = Object.values(properties).every((value) => { - return value !== ''; + const handleConvert = () => { + setOpenDrawer(true); + Object.values(nodes).forEach((node) => { + const nodeProperties = transformObject(node.properties); + if (validateObject(nodeProperties, ServerRequiredFields)) { + Converter.addResourceFromJson({ + ...node, + properties: nodeProperties, + }); + setTerraformCode(Converter.generate()); + } else { + alert('Required fields are missing'); + } }); - - return isValid; }; - // if (Object.values(nodes).length > 0) { - // const node = Object.values(nodes)[0]; - // console.log(node); - // if (validateProperties(node)) { - // Converter.addResourceFromJson(node); - // console.log(Converter.generate()); - // } - // } const handleToggleTheme = () => setThemeMode(themeMode === 'dark' ? 'light' : 'dark'); @@ -70,43 +75,62 @@ export default () => { const openWindow = (url: string) => window.open(url, '_blank')?.focus(); return ( - - - - Cloud Canvas - - - - - - 2D - 3D - - - openWindow(GITHUB_URL)}> - - - - openWindow(NOTION_URL)}> - - - - - {themeMode === 'dark' ? ( - - ) : ( - - )} - - - - + <> + + + + Cloud Canvas + + + + + + 2D + 3D + + + openWindow(GITHUB_URL)} + > + + + + openWindow(NOTION_URL)} + > + + + + + {themeMode === 'dark' ? ( + + ) : ( + + )} + + + + + setOpenDrawer(false)} + /> + ); }; diff --git a/apps/client/src/components/NCloud/PropertiesBar/ServerProperties.tsx b/apps/client/src/components/NCloud/PropertiesBar/ServerProperties.tsx index 31fa5243..cfaa23a8 100644 --- a/apps/client/src/components/NCloud/PropertiesBar/ServerProperties.tsx +++ b/apps/client/src/components/NCloud/PropertiesBar/ServerProperties.tsx @@ -17,17 +17,20 @@ type Props = {}; export default ({}: Props) => { const { selectedResource, updateProperties } = useNCloud(); - const [serverImageCode, setServerImageCode] = useState(''); - const [specCode, setSpecCode] = useState(''); - - const [name, setName] = useState(''); + const [serverImageCode, setServerImageCode] = useState( + selectedResource?.properties.server_image_number ?? '', + ); + const [specCode, setSpecCode] = useState( + selectedResource?.properties.server_spec_code ?? '', + ); + const [name, setName] = useState(selectedResource?.properties.name ?? ''); useEffect(() => { if (!selectedResource) return; const { properties } = selectedResource; - setName(properties.name || ''); - setServerImageCode(properties.server_image_number || ''); - setSpecCode(properties.server_spec_code || ''); + setName(properties.name ?? ''); + setServerImageCode(properties.server_image_number ?? ''); + setSpecCode(properties.server_spec_code ?? ''); }, [selectedResource]); const handleChangeImage = (e: SelectChangeEvent) => { diff --git a/apps/client/src/components/Node/ncloud/ServerNode.tsx b/apps/client/src/components/Node/ncloud/ServerNode.tsx index db871f9f..1d38a11e 100644 --- a/apps/client/src/components/Node/ncloud/ServerNode.tsx +++ b/apps/client/src/components/Node/ncloud/ServerNode.tsx @@ -121,9 +121,7 @@ const Node2D = ({ properties }: Props) => { fill="#d86613" style={{ userSelect: 'none' }} > - {properties?.server_spec_code?.value - .split('-')[0] - .slice(0, 4)} + {properties?.server_spec_code?.split('-')[0].slice(0, 4)} diff --git a/apps/client/src/helpers/group.ts b/apps/client/src/helpers/group.ts index f3ab4183..29cd1e35 100644 --- a/apps/client/src/helpers/group.ts +++ b/apps/client/src/helpers/group.ts @@ -2,6 +2,15 @@ import { GRID_2D_SIZE } from '@constants'; import { Bounds, Dimension, Group } from '@types'; import { convert2dTo3dPoint, convert3dTo2dPoint } from '@utils'; +export const GraphGroup = { + id: '', + type: '', + nodeIds: [], + properties: {}, + childGroupIds: [], + parentGroupId: '', +}; + export const computeBounds = (_bounds: Bounds[], dimension: Dimension) => { const padding = GRID_2D_SIZE * 2; let bounds = _bounds; diff --git a/apps/client/src/helpers/node.ts b/apps/client/src/helpers/node.ts index a99ab6d9..f753b126 100644 --- a/apps/client/src/helpers/node.ts +++ b/apps/client/src/helpers/node.ts @@ -7,6 +7,13 @@ import { convert3dTo2dPoint, } from '@utils'; +export const GraphNode = { + id: '', + type: '', + point: { x: 0, y: 0 }, + connectors: {}, +}; + const getNodeOffsetForDimension = (nodeSize: Size, baseSize: Size) => { return { x: (baseSize.width - nodeSize.width) / 2, diff --git a/apps/client/src/models/ncloud/CloudFunction.ts b/apps/client/src/models/ncloud/CloudFunction.ts new file mode 100644 index 00000000..365537f2 --- /dev/null +++ b/apps/client/src/models/ncloud/CloudFunction.ts @@ -0,0 +1,21 @@ +import { GraphNode } from '@helpers/node'; +import { Node } from '@types'; +import { Networks, NetworksProp } from './Networks'; + +export interface CloudFunctionProp extends NetworksProp { + //TODO: +} + +export const CloudFunctionNode: Node & { + properties: CloudFunctionProp; +} = { + ...GraphNode, + type: 'cloud-function', + size: { + '2d': { width: 90, height: 90 }, + '3d': { width: 96, height: 113.438, offset: 10 }, + }, + properties: { + ...Networks, + }, +}; diff --git a/apps/client/src/models/ncloud/MySQLDB.ts b/apps/client/src/models/ncloud/MySQLDB.ts new file mode 100644 index 00000000..751f2c3d --- /dev/null +++ b/apps/client/src/models/ncloud/MySQLDB.ts @@ -0,0 +1,21 @@ +import { GraphNode } from '@helpers/node'; +import { Node } from '@types'; +import { Networks, NetworksProp } from './Networks'; + +export interface MYSQLDBProp extends NetworksProp { + //TODO: +} + +export const MySQLDBNode: Node = { + ...GraphNode, + type: 'db-mysql', + size: { + '2d': { width: 90, height: 90 }, + '3d': { width: 128, height: 137.5 }, + }, + properties: { + ...Networks, + }, +}; + +export const MySQLDBRequiredFields = {}; diff --git a/apps/client/src/models/ncloud/Networks.ts b/apps/client/src/models/ncloud/Networks.ts new file mode 100644 index 00000000..bc28790a --- /dev/null +++ b/apps/client/src/models/ncloud/Networks.ts @@ -0,0 +1,44 @@ +import { GraphGroup } from '@helpers/group'; +import { Group } from '@types'; + +export type NetworksProp = { + region?: { key: string; value: string }; + subnet?: { key: string; value: string }; + vpc?: { key: string; value: string }; +}; + +export const Networks: NetworksProp = { + region: undefined, + subnet: undefined, + vpc: undefined, +}; + +export const NetworksRequiredFields = { + region: true, + subnet: true, + vpc: true, +}; + +export const RegionGroup: Group = { + ...GraphGroup, + type: 'region', + properties: { + name: '', + }, +}; + +export const VpcGroup: Group = { + ...GraphGroup, + type: 'vpc', + properties: { + name: '', + }, +}; + +export const SubnetGroup: Group = { + ...GraphGroup, + type: 'subnet', + properties: { + name: '', + }, +}; diff --git a/apps/client/src/models/ncloud/Server.ts b/apps/client/src/models/ncloud/Server.ts new file mode 100644 index 00000000..453d6c5f --- /dev/null +++ b/apps/client/src/models/ncloud/Server.ts @@ -0,0 +1,32 @@ +import { GraphNode } from '@helpers/node'; +import { Node } from '@types'; +import { Networks, NetworksProp } from './Networks'; + +export interface ServerProp extends NetworksProp { + name?: string; + server_image_number?: string; + server_spec_code?: string; +} + +export const ServerNode: Node & { + properties: ServerProp; +} = { + ...GraphNode, + type: 'server', + size: { + '2d': { width: 90, height: 90 }, + '3d': { width: 128, height: 111 }, + }, + properties: { + ...Networks, + name: undefined, + server_image_number: undefined, + server_spec_code: undefined, + }, +}; + +export const ServerRequiredFields = { + name: true, + server_image_number: true, + server_spec_code: true, +}; diff --git a/apps/client/src/models/ncloud/index.ts b/apps/client/src/models/ncloud/index.ts index 3647eadd..17600eb5 100644 --- a/apps/client/src/models/ncloud/index.ts +++ b/apps/client/src/models/ncloud/index.ts @@ -1,13 +1,16 @@ -import { Group, Node } from '@types'; +import { CloudFunctionNode } from './CloudFunction'; +import { MySQLDBNode } from './MySQLDB'; +import { RegionGroup, SubnetGroup, VpcGroup } from './Networks'; +import { ServerNode } from './Server'; export const NcloudNodeFactory = (type: string) => { switch (type) { case 'server': - return Server; + return ServerNode; case 'cloud-function': - return CloudFunction; + return CloudFunctionNode; case 'db-mysql': - return MySQLDB; + return MySQLDBNode; default: { throw new Error(`Unknown type: ${type}`); } @@ -17,93 +20,14 @@ export const NcloudNodeFactory = (type: string) => { export const NcloudGroupFactory = (type: string) => { switch (type) { case 'region': - return Region; + return RegionGroup; case 'vpc': - return Vpc; + return VpcGroup; case 'subnet': - return Subnet; + return SubnetGroup; default: { throw new Error(`Unknown type: ${type}`); } } }; - -const GraphNodeProperties = { - id: '', - type: '', - point: { x: 0, y: 0 }, - connectors: {}, -}; - -const GraphGroupProperties = { - id: '', - type: '', - nodeIds: [], - properties: {}, - childGroupIds: [], - parentGroupId: '', -}; - -const Server: Node = { - ...GraphNodeProperties, - type: 'server', - size: { - '2d': { width: 90, height: 90 }, - '3d': { width: 128, height: 111 }, - }, - properties: { - name: undefined, - region: undefined, - subnet: undefined, - vpc: undefined, - server_image_number: undefined, - server_spec_code: undefined, - }, -}; - -const CloudFunction: Node = { - ...GraphNodeProperties, - type: 'cloud-function', - size: { - '2d': { width: 90, height: 90 }, - '3d': { width: 96, height: 113.438, offset: 10 }, - }, - properties: { - region: '', - vpc: '', - subnet: '', - }, -}; - -const MySQLDB: Node = { - ...GraphNodeProperties, - type: 'db-mysql', - size: { - '2d': { width: 90, height: 90 }, - '3d': { width: 128, height: 137.5 }, - }, - properties: { - region: '', - vpc: '', - subnet: '', - }, -}; - -const Region: Group = { - ...GraphGroupProperties, - type: 'region', - properties: { - name: undefined, - }, -}; - -const Vpc: Group = { - ...GraphGroupProperties, - type: 'vpc', -}; - -const Subnet: Group = { - ...GraphGroupProperties, - type: 'subnet', -}; diff --git a/apps/client/src/models/ncloud/utils.ts b/apps/client/src/models/ncloud/utils.ts new file mode 100644 index 00000000..4cac3cb7 --- /dev/null +++ b/apps/client/src/models/ncloud/utils.ts @@ -0,0 +1,31 @@ +export const transformObject = (obj: any) => { + return Object.fromEntries( + Object.entries(obj).map(([key, value]) => { + if ( + typeof value === 'object' && + value !== null && + 'value' in value + ) { + return [key, value.value]; + } + return [key, value]; + }), + ); +}; + +export const validateObject = ( + obj: any, + requiredFields: Record, +) => { + return Object.entries(requiredFields).every(([key, isRequired]) => { + if (!isRequired) { + return true; + } + + const value = obj[key]; + if (typeof value === 'object' && value !== null && 'value' in value) { + return Boolean(value.value); + } + return Boolean(value); + }); +}; From e7bbd16a13581d10cff971ebe9d4d290ece44ae1 Mon Sep 17 00:00:00 2001 From: p1n9d3v Date: Thu, 28 Nov 2024 13:57:27 +0900 Subject: [PATCH 039/124] =?UTF-8?q?=F0=9F=A4=96=20Refactor(client):=20draw?= =?UTF-8?q?er=20=EC=97=B4=EB=95=8C=20=EC=9E=90=EB=8F=99=20=EC=8A=A4?= =?UTF-8?q?=ED=81=AC=EB=A1=A4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/client/package.json | 3 ++ apps/client/src/components/CodeDrawer.tsx | 48 +++++++++---------- .../src/components/Layout/Header/index.tsx | 48 ++++++++++++------- 3 files changed, 58 insertions(+), 41 deletions(-) diff --git a/apps/client/package.json b/apps/client/package.json index ebe1ff6f..5f6fcdb7 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -19,11 +19,14 @@ "nanoid": "^5.0.8", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-syntax-highlighter": "^15.6.1", + "react-type-animation": "^3.2.0", "terraform": "file:../../packages/terraform" }, "devDependencies": { "@types/react": "^18.3.11", "@types/react-dom": "^18.3.1", + "@types/react-syntax-highlighter": "^15.5.13", "@vitejs/plugin-react": "^4.3.3", "serve": "^14.2.4", "vite": "^5.4.9", diff --git a/apps/client/src/components/CodeDrawer.tsx b/apps/client/src/components/CodeDrawer.tsx index 1e6b37db..25355f9f 100644 --- a/apps/client/src/components/CodeDrawer.tsx +++ b/apps/client/src/components/CodeDrawer.tsx @@ -8,10 +8,9 @@ import { Typography, useTheme, } from '@mui/material'; -import { useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import { materialDark } from 'react-syntax-highlighter/dist/esm/styles/prism'; -import { TypeAnimation } from 'react-type-animation'; type Props = { code: string; @@ -23,6 +22,8 @@ export default ({ code, open, onClose }: Props) => { const theme = useTheme(); const [copied, setCopied] = useState(false); const [isFinished, setIsFinished] = useState(false); + const containerRef = useRef(null); + const [isLoaded, setIsLoaded] = useState(false); const handleCopy = () => { navigator.clipboard.writeText(code); @@ -30,9 +31,26 @@ export default ({ code, open, onClose }: Props) => { setTimeout(() => setCopied(false), 2000); // 2초 후 복사 상태 리셋 }; + const scrollToBottom = () => { + if (containerRef.current && !isLoaded) { + containerRef.current.scrollTop = containerRef.current.scrollHeight; + setIsLoaded(true); + } + }; + + useEffect(() => { + if (isLoaded) setIsLoaded(false); + }, [open]); + return ( - + scrollToBottom()} + > { - {!isFinished ? ( - - setIsFinished(true)]} - speed={80} - cursor={true} - style={{ fontSize: '14px', lineHeight: '1.5' }} - repeat={0} - /> - - ) : ( - - {code} - - )} + + {code} + + Date: Thu, 28 Nov 2024 14:05:43 +0900 Subject: [PATCH 040/124] =?UTF-8?q?=F0=9F=A4=96=20Refactor(client):=20scro?= =?UTF-8?q?ll=20smooth=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/client/src/components/CodeDrawer.tsx | 1 + apps/client/src/main.tsx | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/components/CodeDrawer.tsx b/apps/client/src/components/CodeDrawer.tsx index 25355f9f..ec350d5a 100644 --- a/apps/client/src/components/CodeDrawer.tsx +++ b/apps/client/src/components/CodeDrawer.tsx @@ -57,6 +57,7 @@ export default ({ code, open, onClose }: Props) => { backgroundColor: theme.palette.background.default, height: '100%', overflow: 'auto', + scrollBehavior: 'smooth', }} > Date: Thu, 28 Nov 2024 15:56:06 +0900 Subject: [PATCH 041/124] =?UTF-8?q?=E2=9C=A8=20Feat(client):=20loadBalance?= =?UTF-8?q?r=20UI=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/client/src/components/CodeDrawer.tsx | 1 - apps/client/src/components/Node/index.tsx | 3 + .../components/Node/ncloud/LoadBalancer.tsx | 104 ++++++++++++++++++ apps/client/src/constants/index.ts | 10 ++ apps/client/src/models/ncloud/LoadBalancer.ts | 32 ++++++ apps/client/src/models/ncloud/constants.ts | 18 +++ apps/client/src/models/ncloud/index.ts | 3 + 7 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 apps/client/src/components/Node/ncloud/LoadBalancer.tsx create mode 100644 apps/client/src/models/ncloud/LoadBalancer.ts diff --git a/apps/client/src/components/CodeDrawer.tsx b/apps/client/src/components/CodeDrawer.tsx index ec350d5a..974d19bc 100644 --- a/apps/client/src/components/CodeDrawer.tsx +++ b/apps/client/src/components/CodeDrawer.tsx @@ -21,7 +21,6 @@ type Props = { export default ({ code, open, onClose }: Props) => { const theme = useTheme(); const [copied, setCopied] = useState(false); - const [isFinished, setIsFinished] = useState(false); const containerRef = useRef(null); const [isLoaded, setIsLoaded] = useState(false); diff --git a/apps/client/src/components/Node/index.tsx b/apps/client/src/components/Node/index.tsx index 7dfefed9..5d9f2991 100644 --- a/apps/client/src/components/Node/index.tsx +++ b/apps/client/src/components/Node/index.tsx @@ -5,6 +5,7 @@ import ServerNode from '@components/Node/ncloud/ServerNode'; import useDrag from '@hooks/useDrag'; import { Node, Point } from '@types'; import { useEffect } from 'react'; +import LoadBalancer from './ncloud/LoadBalancer'; const nodeFactory = (node: Node) => { switch (node.type) { @@ -16,6 +17,8 @@ const nodeFactory = (node: Node) => { return ; case 'db-mysql': return ; + case 'load-balancer': + return ; default: null; } diff --git a/apps/client/src/components/Node/ncloud/LoadBalancer.tsx b/apps/client/src/components/Node/ncloud/LoadBalancer.tsx new file mode 100644 index 00000000..c2a113f2 --- /dev/null +++ b/apps/client/src/components/Node/ncloud/LoadBalancer.tsx @@ -0,0 +1,104 @@ +import { useDimensionContext } from '@contexts/DimensionContext'; +import { Node } from '@types'; + +type Props = Partial; +const Node3D = ({ properties }: Props) => { + return ( + + + + + + + + + + + + + + + + + + + ); +}; + +const Node2D = ({ properties }: Props) => { + return ( + + + + + ); +}; +export default ({ properties }: Props) => { + const { dimension } = useDimensionContext(); + return dimension === '2d' ? ( + + ) : ( + + ); +}; diff --git a/apps/client/src/constants/index.ts b/apps/client/src/constants/index.ts index 9fb3330d..f015c0e7 100644 --- a/apps/client/src/constants/index.ts +++ b/apps/client/src/constants/index.ts @@ -32,6 +32,16 @@ export const NCLOUD_SERVICES = [ }, ], }, + { + title: 'networks', + items: [ + { + title: 'Load Balancer', + desc: 'load balancing', + type: 'load-balancer', + }, + ], + }, ]; export const NETWORKS_CATEGORIES = [ diff --git a/apps/client/src/models/ncloud/LoadBalancer.ts b/apps/client/src/models/ncloud/LoadBalancer.ts new file mode 100644 index 00000000..d9860567 --- /dev/null +++ b/apps/client/src/models/ncloud/LoadBalancer.ts @@ -0,0 +1,32 @@ +import { GraphNode } from '@helpers/node'; +import { Node } from '@types'; +import { Networks, NetworksProp } from './Networks'; + +export interface LoadBalancerProp extends NetworksProp { + name?: string; + networkType?: string; + subnetNoList?: []; +} + +export const LoadBalancer: Node & { + properties: LoadBalancerProp; +} = { + ...GraphNode, + type: 'load-balancer', + size: { + '2d': { width: 90, height: 90 }, + '3d': { width: 97, height: 94, offset: 10 }, + }, + properties: { + ...Networks, + name: undefined, + networkType: undefined, + subnetNoList: undefined, + }, +}; + +export const LoadBalancerRequiredFields = { + name: true, + networkType: true, + subnetNoList: true, +}; diff --git a/apps/client/src/models/ncloud/constants.ts b/apps/client/src/models/ncloud/constants.ts index e3c6d8d4..acacf85e 100644 --- a/apps/client/src/models/ncloud/constants.ts +++ b/apps/client/src/models/ncloud/constants.ts @@ -31,6 +31,24 @@ export const SERVER_IMAGE_SPEC_CODE: { { code: 'ci48-g3', info: 'vCPU 48EA, Memory 96GB' }, { code: 'ci64-g3', info: 'vCPU 64EA, Memory 128GB' }, ], + '23221307': [ + { code: 'ci2-g3', info: 'vCPU 2EA, Memory 4GB' }, + { code: 'ci4-g3', info: 'vCPU 4EA, Memory 8GB' }, + { code: 'ci8-g3', info: 'vCPU 8EA, Memory 16GB' }, + { code: 'ci16-g3', info: 'vCPU 16EA, Memory 32GB' }, + { code: 'ci32-g3', info: 'vCPU 32EA, Memory 64GB' }, + { code: 'ci48-g3', info: 'vCPU 48EA, Memory 96GB' }, + { code: 'ci64-g3', info: 'vCPU 64EA, Memory 128GB' }, + ], + '23214590': [ + { code: 'ci2-g3', info: 'vCPU 2EA, Memory 4GB' }, + { code: 'ci4-g3', info: 'vCPU 4EA, Memory 8GB' }, + { code: 'ci8-g3', info: 'vCPU 8EA, Memory 16GB' }, + { code: 'ci16-g3', info: 'vCPU 16EA, Memory 32GB' }, + { code: 'ci32-g3', info: 'vCPU 32EA, Memory 64GB' }, + { code: 'ci48-g3', info: 'vCPU 48EA, Memory 96GB' }, + { code: 'ci64-g3', info: 'vCPU 64EA, Memory 128GB' }, + ], }; const krRegionId = `kr-${nanoid()}`; diff --git a/apps/client/src/models/ncloud/index.ts b/apps/client/src/models/ncloud/index.ts index 17600eb5..16356f48 100644 --- a/apps/client/src/models/ncloud/index.ts +++ b/apps/client/src/models/ncloud/index.ts @@ -1,4 +1,5 @@ import { CloudFunctionNode } from './CloudFunction'; +import { LoadBalancer } from './LoadBalancer'; import { MySQLDBNode } from './MySQLDB'; import { RegionGroup, SubnetGroup, VpcGroup } from './Networks'; import { ServerNode } from './Server'; @@ -11,6 +12,8 @@ export const NcloudNodeFactory = (type: string) => { return CloudFunctionNode; case 'db-mysql': return MySQLDBNode; + case 'load-balancer': + return LoadBalancer; default: { throw new Error(`Unknown type: ${type}`); } From dcc4202a61419ee98a23ea282edcbbf7b8d16a5b Mon Sep 17 00:00:00 2001 From: p1n9d3v Date: Fri, 29 Nov 2024 03:19:48 +0900 Subject: [PATCH 042/124] =?UTF-8?q?=F0=9F=A4=96=20Refactor(client):=20regi?= =?UTF-8?q?on=20isometric=20=EA=B3=B5=EC=8B=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/Group/ncloud/RegionGroup.tsx | 39 ++++--------------- 1 file changed, 7 insertions(+), 32 deletions(-) diff --git a/apps/client/src/components/Group/ncloud/RegionGroup.tsx b/apps/client/src/components/Group/ncloud/RegionGroup.tsx index 2d222447..3aa8665c 100644 --- a/apps/client/src/components/Group/ncloud/RegionGroup.tsx +++ b/apps/client/src/components/Group/ncloud/RegionGroup.tsx @@ -10,38 +10,12 @@ interface Props extends Partial { } const Region3D = ({ bounds, properties, color }: Props) => { - const topLeftGrid = screenToGrid2d({ x: 0, y: 0 }); - const topRightGrid = screenToGrid2d({ x: bounds.width, y: 0 }); - const bottomRightGrid = screenToGrid2d({ - x: bounds.width, - y: bounds.height, - }); - const bottomLeftGrid = screenToGrid2d({ x: 0, y: bounds.height }); - - const point1 = gridToScreen3d({ - col: topLeftGrid.col + 1, - row: topLeftGrid.row, - }); - const point2 = gridToScreen3d({ - col: topRightGrid.col + 1, - row: topRightGrid.row, - }); - const point3 = gridToScreen3d({ - col: bottomRightGrid.col + 1, - row: bottomRightGrid.row, - }); - const point4 = gridToScreen3d({ - col: bottomLeftGrid.col + 1, - row: bottomLeftGrid.row, - }); - - const points = ` - ${point1.x} ${point1.y}, - ${point2.x} ${point2.y}, - ${point3.x} ${point3.y}, - ${point4.x} ${point4.y} - `; - + const isoMatrix = new DOMMatrix() + .rotate(30) + .skewX(-30) + .scale(1, 0.8602) + .translate(0, 0); + const points = `0 0, 0 ${bounds.height}, ${bounds.width} ${bounds.height}, ${bounds.width} 0`; return ( <> { stroke={color} strokeWidth="8" fill="none" + transform={isoMatrix.toString()} > From 127fb30b0f6aafcb9e7ca62a86eed0b70865fbbb Mon Sep 17 00:00:00 2001 From: p1n9d3v Date: Fri, 29 Nov 2024 03:32:58 +0900 Subject: [PATCH 043/124] =?UTF-8?q?=F0=9F=A4=96=20Refactor(client):=20grou?= =?UTF-8?q?p=20=EA=B3=84=EC=82=B0=20=EB=B0=A9=EC=8B=9D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD,=20ContainerRegistry=EC=99=80=20=EA=B0=99=EC=9D=B4=20?= =?UTF-8?q?=EB=85=B8=EB=93=9C=EC=9D=98=20=ED=81=AC=EA=B8=B0=EA=B0=80=20?= =?UTF-8?q?=EB=8B=A4=EB=A5=BC=20=EB=96=84=20bounds=EC=99=80=20conenctor?= =?UTF-8?q?=EA=B0=80=20=EC=A0=9C=EC=9E=90=EB=A6=AC=EB=A5=B4=20=EB=AA=BB?= =?UTF-8?q?=EC=B0=BE=EC=95=84=EA=B0=90(=EB=AC=B8=EC=A0=9C=ED=95=B4?= =?UTF-8?q?=EA=B2=B0=ED=95=84=EC=9A=94)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/client/src/CloudGraph.tsx | 3 + .../components/Group/ncloud/RegionGroup.tsx | 39 ++++++-- .../components/Group/ncloud/SubnetGroup.tsx | 49 +++------- .../src/components/Group/ncloud/Title.tsx | 2 +- .../src/components/Group/ncloud/VPCGroup.tsx | 49 +++------- .../src/components/Layout/Header/index.tsx | 9 +- apps/client/src/components/Node/index.tsx | 3 + .../Node/ncloud/ContainerRegistry.tsx | 89 +++++++++++++++++++ apps/client/src/constants/index.ts | 45 ++++++++++ .../client/src/contexts/NodeContext/index.tsx | 2 +- apps/client/src/helpers/node.ts | 2 + .../src/models/ncloud/ContainerRegistry.ts | 23 +++++ apps/client/src/models/ncloud/index.ts | 3 + apps/client/src/types/index.ts | 7 +- apps/client/src/utils/index.ts | 6 +- 15 files changed, 238 insertions(+), 93 deletions(-) create mode 100644 apps/client/src/components/Node/ncloud/ContainerRegistry.tsx create mode 100644 apps/client/src/models/ncloud/ContainerRegistry.ts diff --git a/apps/client/src/CloudGraph.tsx b/apps/client/src/CloudGraph.tsx index 2bf94889..c3d7f49c 100644 --- a/apps/client/src/CloudGraph.tsx +++ b/apps/client/src/CloudGraph.tsx @@ -5,6 +5,7 @@ import Edge from '@components/Edge'; import Graph from '@components/Graph'; import GridBackground from '@components/GridBackground'; import Group from '@components/Group'; +import LargeNode from '@components/LargeNode'; import Node from '@components/Node'; import { useEdgeContext } from '@contexts/EdgeContext'; import { useGroupContext } from '@contexts/GroupContext'; @@ -12,6 +13,8 @@ import { useNodeContext } from '@contexts/NodeContext'; import useConnection from '@hooks/useConnection'; import useGraph from '@hooks/useGraph'; import useSelection from '@hooks/useSelection'; +import { Iso } from '@mui/icons-material'; +import { getConnectorPoints } from '@utils'; import { useEffect, useLayoutEffect } from 'react'; export default () => { diff --git a/apps/client/src/components/Group/ncloud/RegionGroup.tsx b/apps/client/src/components/Group/ncloud/RegionGroup.tsx index 3aa8665c..0e56d74b 100644 --- a/apps/client/src/components/Group/ncloud/RegionGroup.tsx +++ b/apps/client/src/components/Group/ncloud/RegionGroup.tsx @@ -10,12 +10,38 @@ interface Props extends Partial { } const Region3D = ({ bounds, properties, color }: Props) => { - const isoMatrix = new DOMMatrix() - .rotate(30) - .skewX(-30) - .scale(1, 0.8602) - .translate(0, 0); - const points = `0 0, 0 ${bounds.height}, ${bounds.width} ${bounds.height}, ${bounds.width} 0`; + const topLeftGrid = screenToGrid2d({ x: 0, y: 0 }); + const topRightGrid = screenToGrid2d({ x: bounds.width, y: 0 }); + const bottomRightGrid = screenToGrid2d({ + x: bounds.width, + y: bounds.height, + }); + const bottomLeftGrid = screenToGrid2d({ x: 0, y: bounds.height }); + + const point1 = gridToScreen3d({ + col: topLeftGrid.col, + row: topLeftGrid.row, + }); + const point2 = gridToScreen3d({ + col: topRightGrid.col, + row: topRightGrid.row, + }); + const point3 = gridToScreen3d({ + col: bottomRightGrid.col, + row: bottomRightGrid.row, + }); + const point4 = gridToScreen3d({ + col: bottomLeftGrid.col, + row: bottomLeftGrid.row, + }); + + const points = ` + ${point1.x} ${point1.y}, + ${point2.x} ${point2.y}, + ${point3.x} ${point3.y}, + ${point4.x} ${point4.y} + `; + return ( <> { stroke={color} strokeWidth="8" fill="none" - transform={isoMatrix.toString()} > diff --git a/apps/client/src/components/Group/ncloud/SubnetGroup.tsx b/apps/client/src/components/Group/ncloud/SubnetGroup.tsx index 2d222447..b04e9802 100644 --- a/apps/client/src/components/Group/ncloud/SubnetGroup.tsx +++ b/apps/client/src/components/Group/ncloud/SubnetGroup.tsx @@ -1,7 +1,7 @@ import Text from '@components/Group/ncloud/Title'; import { useDimensionContext } from '@contexts/DimensionContext'; import { Bounds, Group } from '@types'; -import { generateRandomRGB, gridToScreen3d, screenToGrid2d } from '@utils'; +import { generateRandomRGB } from '@utils'; import { useMemo } from 'react'; interface Props extends Partial { @@ -9,39 +9,13 @@ interface Props extends Partial { bounds: Bounds; } -const Region3D = ({ bounds, properties, color }: Props) => { - const topLeftGrid = screenToGrid2d({ x: 0, y: 0 }); - const topRightGrid = screenToGrid2d({ x: bounds.width, y: 0 }); - const bottomRightGrid = screenToGrid2d({ - x: bounds.width, - y: bounds.height, - }); - const bottomLeftGrid = screenToGrid2d({ x: 0, y: bounds.height }); - - const point1 = gridToScreen3d({ - col: topLeftGrid.col + 1, - row: topLeftGrid.row, - }); - const point2 = gridToScreen3d({ - col: topRightGrid.col + 1, - row: topRightGrid.row, - }); - const point3 = gridToScreen3d({ - col: bottomRightGrid.col + 1, - row: bottomRightGrid.row, - }); - const point4 = gridToScreen3d({ - col: bottomLeftGrid.col + 1, - row: bottomLeftGrid.row, - }); - - const points = ` - ${point1.x} ${point1.y}, - ${point2.x} ${point2.y}, - ${point3.x} ${point3.y}, - ${point4.x} ${point4.y} - `; - +const Subnet3D = ({ bounds, properties, color }: Props) => { + const isoMatrix = new DOMMatrix() + .rotate(30) + .skewX(-30) + .scale(1, 0.8602) + .translate(0, 0); + const points = `0 0, 0 ${bounds.height}, ${bounds.width} ${bounds.height}, ${bounds.width} 0`; return ( <> { stroke={color} strokeWidth="8" fill="none" + transform={isoMatrix.toString()} > ); }; -const Region2D = ({ bounds, color, properties }: Props) => { +const Subnet2D = ({ bounds, color, properties }: Props) => { const points = `0 0, 0 ${bounds.height}, ${bounds.width} ${bounds.height}, ${bounds.width} 0`; return ( @@ -76,8 +51,8 @@ export default ({ bounds, properties }: Omit) => { const color = useMemo(() => generateRandomRGB(), []); return dimension === '2d' ? ( - + ) : ( - + ); }; diff --git a/apps/client/src/components/Group/ncloud/Title.tsx b/apps/client/src/components/Group/ncloud/Title.tsx index 6ba19d10..f76a5310 100644 --- a/apps/client/src/components/Group/ncloud/Title.tsx +++ b/apps/client/src/components/Group/ncloud/Title.tsx @@ -24,7 +24,7 @@ export default ({ bounds, color, text }: Props) => { const rectWidth = fontSize * textLength; const rectHeight = 50; const rectY = 10; - const rectX = dimension === '2d' ? 10 : 90; + const rectX = 10; const matrix = convertToIsoMatrix(bounds.x, bounds.y).toString(); const centerX = rectWidth / 2 + rectX; diff --git a/apps/client/src/components/Group/ncloud/VPCGroup.tsx b/apps/client/src/components/Group/ncloud/VPCGroup.tsx index 2d222447..12126cdf 100644 --- a/apps/client/src/components/Group/ncloud/VPCGroup.tsx +++ b/apps/client/src/components/Group/ncloud/VPCGroup.tsx @@ -1,7 +1,7 @@ import Text from '@components/Group/ncloud/Title'; import { useDimensionContext } from '@contexts/DimensionContext'; import { Bounds, Group } from '@types'; -import { generateRandomRGB, gridToScreen3d, screenToGrid2d } from '@utils'; +import { generateRandomRGB } from '@utils'; import { useMemo } from 'react'; interface Props extends Partial { @@ -9,39 +9,13 @@ interface Props extends Partial { bounds: Bounds; } -const Region3D = ({ bounds, properties, color }: Props) => { - const topLeftGrid = screenToGrid2d({ x: 0, y: 0 }); - const topRightGrid = screenToGrid2d({ x: bounds.width, y: 0 }); - const bottomRightGrid = screenToGrid2d({ - x: bounds.width, - y: bounds.height, - }); - const bottomLeftGrid = screenToGrid2d({ x: 0, y: bounds.height }); - - const point1 = gridToScreen3d({ - col: topLeftGrid.col + 1, - row: topLeftGrid.row, - }); - const point2 = gridToScreen3d({ - col: topRightGrid.col + 1, - row: topRightGrid.row, - }); - const point3 = gridToScreen3d({ - col: bottomRightGrid.col + 1, - row: bottomRightGrid.row, - }); - const point4 = gridToScreen3d({ - col: bottomLeftGrid.col + 1, - row: bottomLeftGrid.row, - }); - - const points = ` - ${point1.x} ${point1.y}, - ${point2.x} ${point2.y}, - ${point3.x} ${point3.y}, - ${point4.x} ${point4.y} - `; - +const VPC3D = ({ bounds, properties, color }: Props) => { + const isoMatrix = new DOMMatrix() + .rotate(30) + .skewX(-30) + .scale(1, 0.8602) + .translate(0, 0); + const points = `0 0, 0 ${bounds.height}, ${bounds.width} ${bounds.height}, ${bounds.width} 0`; return ( <> { stroke={color} strokeWidth="8" fill="none" + transform={isoMatrix.toString()} > ); }; -const Region2D = ({ bounds, color, properties }: Props) => { +const VPC2D = ({ bounds, color, properties }: Props) => { const points = `0 0, 0 ${bounds.height}, ${bounds.width} ${bounds.height}, ${bounds.width} 0`; return ( @@ -76,8 +51,8 @@ export default ({ bounds, properties }: Omit) => { const color = useMemo(() => generateRandomRGB(), []); return dimension === '2d' ? ( - + ) : ( - + ); }; diff --git a/apps/client/src/components/Layout/Header/index.tsx b/apps/client/src/components/Layout/Header/index.tsx index ad0bc0dc..fba89b78 100644 --- a/apps/client/src/components/Layout/Header/index.tsx +++ b/apps/client/src/components/Layout/Header/index.tsx @@ -1,14 +1,9 @@ import { ServerRequiredFields } from '@/src/models/ncloud/Server'; import { transformObject, validateObject } from '@/src/models/ncloud/utils'; import CodeDrawer from '@components/CodeDrawer'; -import ServerNode from '@components/Node/ncloud/ServerNode'; import { useDimensionContext } from '@contexts/DimensionContext'; import { useNodeContext } from '@contexts/NodeContext'; import useNCloud from '@hooks/useNCloud'; -import { - SettingsInputComponent, - SettingsPhoneSharp, -} from '@mui/icons-material'; import DarkModeIcon from '@mui/icons-material/DarkMode'; import GitHubIcon from '@mui/icons-material/GitHub'; import LightModeIcon from '@mui/icons-material/LightMode'; @@ -21,9 +16,8 @@ import IconButton from '@mui/material/IconButton'; import Stack from '@mui/material/Stack'; import { styled, useColorScheme } from '@mui/material/styles'; import Typography from '@mui/material/Typography'; -import { Node } from '@types'; import { TerraformConvertor } from 'node_modules/terraform/convertor/TerraformConvertor'; -import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useState } from 'react'; const StyledBox = styled(Box)(({ theme }) => ({ display: 'flex', @@ -66,7 +60,6 @@ export default () => { properties: transformObject(selectedResource.properties), }; - console.log(nodeProperties); if (!validateObject(nodeProperties.properties, ServerRequiredFields)) { alert('Is not valid'); return; diff --git a/apps/client/src/components/Node/index.tsx b/apps/client/src/components/Node/index.tsx index 5d9f2991..fc2c9a2b 100644 --- a/apps/client/src/components/Node/index.tsx +++ b/apps/client/src/components/Node/index.tsx @@ -6,6 +6,7 @@ import useDrag from '@hooks/useDrag'; import { Node, Point } from '@types'; import { useEffect } from 'react'; import LoadBalancer from './ncloud/LoadBalancer'; +import ContainerRegistry from './ncloud/ContainerRegistry'; const nodeFactory = (node: Node) => { switch (node.type) { @@ -19,6 +20,8 @@ const nodeFactory = (node: Node) => { return ; case 'load-balancer': return ; + // case 'container-registry': + // return ; default: null; } diff --git a/apps/client/src/components/Node/ncloud/ContainerRegistry.tsx b/apps/client/src/components/Node/ncloud/ContainerRegistry.tsx new file mode 100644 index 00000000..206cda70 --- /dev/null +++ b/apps/client/src/components/Node/ncloud/ContainerRegistry.tsx @@ -0,0 +1,89 @@ +import { useDimensionContext } from '@contexts/DimensionContext'; +import { Node } from '@types'; + +type Props = Partial; +//TODO: +const Node3D = ({ properties }: Props) => { + const width = 512; + const height = 296; + const point = `${width / 2},0 ${width},${height / 2} ${width / 2},${height} 0,${height / 2}`; + const isoMatrix = new DOMMatrix() + .rotate(30) + .skewX(-30) + .scale(1, 0.8602) + .translate(0, 0); + return ( + + ); +}; + +const Node2D = ({ properties }: Props) => { + return ( + + + + + + + + + + + + + + + ECS Cluster + + + ECS Cluster + + + + + + ); +}; +export default ({ properties }: Props) => { + const { dimension } = useDimensionContext(); + return dimension === '2d' ? ( + + ) : ( + + ); +}; diff --git a/apps/client/src/constants/index.ts b/apps/client/src/constants/index.ts index f015c0e7..9f24032b 100644 --- a/apps/client/src/constants/index.ts +++ b/apps/client/src/constants/index.ts @@ -22,6 +22,31 @@ export const NCLOUD_SERVICES = [ }, ], }, + // { + // title: 'container', + // items: [ + // { + // title: 'Container Registry', + // desc: 'Container Registry', + // type: 'container-registry', + // }, + // { + // title: 'Kubernetes', + // desc: 'NCloud Kubernetes Service', + // type: 'Kubernetes', + // }, + // ], + // }, + { + title: 'storage', + items: [ + { + title: 'Object Storage', + desc: 'Object Storage', + type: 'object-storage', + }, + ], + }, { title: 'database', items: [ @@ -30,6 +55,26 @@ export const NCLOUD_SERVICES = [ desc: 'Managed MySQL database', type: 'db-mysql', }, + // { + // title: 'DB for Redis', + // desc: 'Managed Redis database', + // type: 'db-redis', + // }, + // { + // title: 'DB for MSSQL', + // desc: 'Managed MSSQL database', + // type: 'db-mssql', + // }, + // { + // title: 'DB for MongoDB', + // desc: 'Managed MongoDB database', + // type: 'db-mongo', + // }, + // { + // title: 'DB for PostgreSQL', + // desc: 'Managed PostgreSQL database', + // type: 'db-postgres', + // }, ], }, { diff --git a/apps/client/src/contexts/NodeContext/index.tsx b/apps/client/src/contexts/NodeContext/index.tsx index 4512c4cf..2f7a0ee9 100644 --- a/apps/client/src/contexts/NodeContext/index.tsx +++ b/apps/client/src/contexts/NodeContext/index.tsx @@ -20,7 +20,7 @@ type NodeContextProps = { const NodeContext = createContext(undefined); const initialState: NodeState = { - nodes: {}, + nodes: mockInitialState.nodes, }; export const NodeProvider = ({ children }: { children: ReactNode }) => { diff --git a/apps/client/src/helpers/node.ts b/apps/client/src/helpers/node.ts index f753b126..cdf97864 100644 --- a/apps/client/src/helpers/node.ts +++ b/apps/client/src/helpers/node.ts @@ -14,6 +14,7 @@ export const GraphNode = { connectors: {}, }; +//TODO: 사이즈가 큰 노드를 다룰 떄 이상해짐 const getNodeOffsetForDimension = (nodeSize: Size, baseSize: Size) => { return { x: (baseSize.width - nodeSize.width) / 2, @@ -30,6 +31,7 @@ export const adjustNodePointForDimension = ( const { point, size } = node; const offset = getNodeOffsetForDimension(size['3d'], NODE_BASE_SIZE['3d']); + console.log('offset', node.type, offset); let result; if (dimension === '2d') { result = convert3dTo2dPoint({ diff --git a/apps/client/src/models/ncloud/ContainerRegistry.ts b/apps/client/src/models/ncloud/ContainerRegistry.ts new file mode 100644 index 00000000..8a851d76 --- /dev/null +++ b/apps/client/src/models/ncloud/ContainerRegistry.ts @@ -0,0 +1,23 @@ +import { GraphNode } from '@helpers/node'; +import { Node } from '@types'; +import { Networks, NetworksProp } from './Networks'; + +export interface ContainerRegistryProp extends NetworksProp { + //TODO: +} + +export const ContainerRegistryNode: Node & { + properties: ContainerRegistryProp; +} = { + ...GraphNode, + type: 'container-registry', + size: { + '2d': { width: 360, height: 360 }, + '3d': { width: 360, height: 360 }, + }, + properties: { + ...Networks, + }, +}; + +export const ContainerRegistryRequiredFields = {}; diff --git a/apps/client/src/models/ncloud/index.ts b/apps/client/src/models/ncloud/index.ts index 16356f48..d26ab3d1 100644 --- a/apps/client/src/models/ncloud/index.ts +++ b/apps/client/src/models/ncloud/index.ts @@ -3,6 +3,7 @@ import { LoadBalancer } from './LoadBalancer'; import { MySQLDBNode } from './MySQLDB'; import { RegionGroup, SubnetGroup, VpcGroup } from './Networks'; import { ServerNode } from './Server'; +import { ContainerRegistryNode } from './ContainerRegistry'; export const NcloudNodeFactory = (type: string) => { switch (type) { @@ -14,6 +15,8 @@ export const NcloudNodeFactory = (type: string) => { return MySQLDBNode; case 'load-balancer': return LoadBalancer; + case 'container-registry': + return ContainerRegistryNode; default: { throw new Error(`Unknown type: ${type}`); } diff --git a/apps/client/src/types/index.ts b/apps/client/src/types/index.ts index e31a3952..939ee9a8 100644 --- a/apps/client/src/types/index.ts +++ b/apps/client/src/types/index.ts @@ -4,7 +4,12 @@ export type Point = { x: number; y: number }; export type GridPoint = { col: number; row: number }; -export type Size = { width: number; height: number; offset?: number }; +export type Size = { + width: number; + height: number; + offset?: number; + depth?: number; +}; export type ViewBox = Point & Size; diff --git a/apps/client/src/utils/index.ts b/apps/client/src/utils/index.ts index e0a1638e..a694e60f 100644 --- a/apps/client/src/utils/index.ts +++ b/apps/client/src/utils/index.ts @@ -81,7 +81,11 @@ export const convert3dTo2dPoint = (point: Point) => { }; export const convert2dTo3dPoint = (point: Point) => { - return gridToScreen3d(screenToGrid2d(point)); + const grid = screenToGrid2d(point); + return gridToScreen3d({ + col: grid.col + 1, + row: grid.row, + }); }; export const generateRandomRGB = () => { From 2e4f043c3a50677b8120bc98a14fb5b2d03ff59c Mon Sep 17 00:00:00 2001 From: p1n9d3v Date: Fri, 29 Nov 2024 04:13:23 +0900 Subject: [PATCH 044/124] =?UTF-8?q?=F0=9F=A4=96=20Refactor(client):=20?= =?UTF-8?q?=EC=9E=84=EC=8B=9C=20fetch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/client/src/App.tsx | 9 ++++ apps/client/src/CloudGraph.tsx | 3 -- .../components/NCloud/PropertiesBar/index.tsx | 26 ++++++++++- .../client/src/contexts/NodeContext/index.tsx | 3 +- apps/client/src/hooks/usePost.ts | 46 +++++++++++++++++++ apps/client/vite.config.ts | 3 ++ 6 files changed, 83 insertions(+), 7 deletions(-) create mode 100644 apps/client/src/hooks/usePost.ts diff --git a/apps/client/src/App.tsx b/apps/client/src/App.tsx index 4058d78a..d1d18a57 100644 --- a/apps/client/src/App.tsx +++ b/apps/client/src/App.tsx @@ -3,9 +3,18 @@ import Header from '@components/Layout/Header'; import Sidebar from '@components/Layout/Sidebar'; import NetworksBar from '@components/NCloud/NetworksBar/index'; import PropertiesBar from '@components/NCloud/PropertiesBar'; +import usePost from '@hooks/usePost'; import Box from '@mui/material/Box'; +import { useEffect } from 'react'; function App() { + const { data, postData } = usePost( + 'https://api.cloudcanvas.kro.kr/auth/login', + ); + console.log(data); + useEffect(() => { + postData({}); + }, []); return ( <> { diff --git a/apps/client/src/components/NCloud/PropertiesBar/index.tsx b/apps/client/src/components/NCloud/PropertiesBar/index.tsx index fefff6b7..15f52d9e 100644 --- a/apps/client/src/components/NCloud/PropertiesBar/index.tsx +++ b/apps/client/src/components/NCloud/PropertiesBar/index.tsx @@ -1,9 +1,31 @@ import ServerProperties from '@components/NCloud/PropertiesBar/ServerProperties'; import useNCloud from '@hooks/useNCloud'; -import { AppBar, Toolbar } from '@mui/material'; +import { AppBar, Toolbar, Typography } from '@mui/material'; + +const PropertiesFactory = (type: string) => { + switch (type) { + case 'server': { + return ; + } + default: { + return ( + + 아직 개발중 입니다. + + ); + } + } +}; + export default () => { const { selectedResource } = useNCloud(); + if (!selectedResource) return; + return ( { paddingY: 2, }} > - + {PropertiesFactory(selectedResource.type)} ); diff --git a/apps/client/src/contexts/NodeContext/index.tsx b/apps/client/src/contexts/NodeContext/index.tsx index 2f7a0ee9..abdcfe99 100644 --- a/apps/client/src/contexts/NodeContext/index.tsx +++ b/apps/client/src/contexts/NodeContext/index.tsx @@ -1,4 +1,3 @@ -import { mockInitialState } from '../../../mocks'; import { NodeAction, nodeReducer, @@ -20,7 +19,7 @@ type NodeContextProps = { const NodeContext = createContext(undefined); const initialState: NodeState = { - nodes: mockInitialState.nodes, + nodes: {}, }; export const NodeProvider = ({ children }: { children: ReactNode }) => { diff --git a/apps/client/src/hooks/usePost.ts b/apps/client/src/hooks/usePost.ts new file mode 100644 index 00000000..047e78ce --- /dev/null +++ b/apps/client/src/hooks/usePost.ts @@ -0,0 +1,46 @@ +import { useState } from 'react'; + +type State = { + data: T | null; + loading: boolean; + error: string | null; + postData: (body: any) => Promise; +}; + +const usePost = (url: string): State => { + const [data, setData] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + const postData = async (body: any) => { + setLoading(true); + setError(null); + + try { + const response = await fetch(url, { + method: 'POST', + // headers: { + // 'Content-Type': 'application/json', + // }, + credentials: 'include', + // body: JSON.stringify(body), + }); + + if (!response.ok) { + const errorMessage = await response.text(); + throw new Error(errorMessage || 'Error posting data'); + } + + const responseData = await response.json(); + setData(responseData); + } catch (err: any) { + setError(err.message || 'Unexpected Error'); + } finally { + setLoading(false); + } + }; + + return { data, loading, error, postData }; +}; + +export default usePost; diff --git a/apps/client/vite.config.ts b/apps/client/vite.config.ts index e8f2a5b1..0ba4ab4a 100644 --- a/apps/client/vite.config.ts +++ b/apps/client/vite.config.ts @@ -5,4 +5,7 @@ import tsconfigPaths from 'vite-tsconfig-paths'; // https://vite.dev/config/ export default defineConfig({ plugins: [react(), tsconfigPaths()], + server: { + port: 3001, + }, }); From f5534f64c77c60e74951807fc693538c9c8e214f Mon Sep 17 00:00:00 2001 From: p1n9d3v Date: Fri, 29 Nov 2024 04:51:13 +0900 Subject: [PATCH 045/124] =?UTF-8?q?=E2=9C=A8=20Feat(client):=20storage=20N?= =?UTF-8?q?ode=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/client/src/App.tsx | 9 -- apps/client/src/components/Node/index.tsx | 3 +- .../Node/ncloud/LoadBalancerNode.tsx | 104 ++++++++++++++++++ .../{LoadBalancer.ts => LoadBalancerNode.ts} | 2 +- .../src/models/ncloud/ObjectStorage.tsx | 21 ++++ apps/client/src/models/ncloud/index.ts | 7 +- 6 files changed, 133 insertions(+), 13 deletions(-) create mode 100644 apps/client/src/components/Node/ncloud/LoadBalancerNode.tsx rename apps/client/src/models/ncloud/{LoadBalancer.ts => LoadBalancerNode.ts} (94%) create mode 100644 apps/client/src/models/ncloud/ObjectStorage.tsx diff --git a/apps/client/src/App.tsx b/apps/client/src/App.tsx index d1d18a57..4058d78a 100644 --- a/apps/client/src/App.tsx +++ b/apps/client/src/App.tsx @@ -3,18 +3,9 @@ import Header from '@components/Layout/Header'; import Sidebar from '@components/Layout/Sidebar'; import NetworksBar from '@components/NCloud/NetworksBar/index'; import PropertiesBar from '@components/NCloud/PropertiesBar'; -import usePost from '@hooks/usePost'; import Box from '@mui/material/Box'; -import { useEffect } from 'react'; function App() { - const { data, postData } = usePost( - 'https://api.cloudcanvas.kro.kr/auth/login', - ); - console.log(data); - useEffect(() => { - postData({}); - }, []); return ( <> { switch (node.type) { @@ -20,6 +19,8 @@ const nodeFactory = (node: Node) => { return ; case 'load-balancer': return ; + case 'object-storage': + return ; // case 'container-registry': // return ; default: diff --git a/apps/client/src/components/Node/ncloud/LoadBalancerNode.tsx b/apps/client/src/components/Node/ncloud/LoadBalancerNode.tsx new file mode 100644 index 00000000..c2a113f2 --- /dev/null +++ b/apps/client/src/components/Node/ncloud/LoadBalancerNode.tsx @@ -0,0 +1,104 @@ +import { useDimensionContext } from '@contexts/DimensionContext'; +import { Node } from '@types'; + +type Props = Partial; +const Node3D = ({ properties }: Props) => { + return ( + + + + + + + + + + + + + + + + + + + ); +}; + +const Node2D = ({ properties }: Props) => { + return ( + + + + + ); +}; +export default ({ properties }: Props) => { + const { dimension } = useDimensionContext(); + return dimension === '2d' ? ( + + ) : ( + + ); +}; diff --git a/apps/client/src/models/ncloud/LoadBalancer.ts b/apps/client/src/models/ncloud/LoadBalancerNode.ts similarity index 94% rename from apps/client/src/models/ncloud/LoadBalancer.ts rename to apps/client/src/models/ncloud/LoadBalancerNode.ts index d9860567..f4f994ee 100644 --- a/apps/client/src/models/ncloud/LoadBalancer.ts +++ b/apps/client/src/models/ncloud/LoadBalancerNode.ts @@ -8,7 +8,7 @@ export interface LoadBalancerProp extends NetworksProp { subnetNoList?: []; } -export const LoadBalancer: Node & { +export const LoadBalancerNode: Node & { properties: LoadBalancerProp; } = { ...GraphNode, diff --git a/apps/client/src/models/ncloud/ObjectStorage.tsx b/apps/client/src/models/ncloud/ObjectStorage.tsx new file mode 100644 index 00000000..9a034495 --- /dev/null +++ b/apps/client/src/models/ncloud/ObjectStorage.tsx @@ -0,0 +1,21 @@ +import { GraphNode } from '@helpers/node'; +import { Node } from '@types'; +import { Networks, NetworksProp } from './Networks'; + +export interface ObjectStorageProp extends NetworksProp { + //TODO: +} + +export const ObjectStorageNode: Node & { + properties: ObjectStorageProp; +} = { + ...GraphNode, + type: 'object-storage', + size: { + '2d': { width: 90, height: 90 }, + '3d': { width: 100.626, height: 115.695, offset: 20 }, + }, + properties: { + ...Networks, + }, +}; diff --git a/apps/client/src/models/ncloud/index.ts b/apps/client/src/models/ncloud/index.ts index d26ab3d1..338fb4b0 100644 --- a/apps/client/src/models/ncloud/index.ts +++ b/apps/client/src/models/ncloud/index.ts @@ -1,9 +1,10 @@ import { CloudFunctionNode } from './CloudFunction'; -import { LoadBalancer } from './LoadBalancer'; +import { LoadBalancerNode } from './LoadBalancerNode'; import { MySQLDBNode } from './MySQLDB'; import { RegionGroup, SubnetGroup, VpcGroup } from './Networks'; import { ServerNode } from './Server'; import { ContainerRegistryNode } from './ContainerRegistry'; +import { ObjectStorageNode } from './ObjectStorage'; export const NcloudNodeFactory = (type: string) => { switch (type) { @@ -14,9 +15,11 @@ export const NcloudNodeFactory = (type: string) => { case 'db-mysql': return MySQLDBNode; case 'load-balancer': - return LoadBalancer; + return LoadBalancerNode; case 'container-registry': return ContainerRegistryNode; + case 'object-storage': + return ObjectStorageNode; default: { throw new Error(`Unknown type: ${type}`); } From a69d4f0c7779006f85fba95d96b02b9144f90c33 Mon Sep 17 00:00:00 2001 From: p1n9d3v Date: Fri, 29 Nov 2024 04:52:29 +0900 Subject: [PATCH 046/124] =?UTF-8?q?=F0=9F=A4=96=20Refactor(client):=20sear?= =?UTF-8?q?ch=20=EC=A3=BC=EC=84=9D=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/Layout/Sidebar/index.tsx | 23 ++++++++----------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/apps/client/src/components/Layout/Sidebar/index.tsx b/apps/client/src/components/Layout/Sidebar/index.tsx index 1b2faced..294262de 100644 --- a/apps/client/src/components/Layout/Sidebar/index.tsx +++ b/apps/client/src/components/Layout/Sidebar/index.tsx @@ -17,11 +17,6 @@ const CLOUD_PLATFORMS = [ title: 'Naver Cloud Platform', imgUrl: 'https://pbs.twimg.com/profile_images/1513858761076604929/O7RUa3BX_400x400.jpg', }, - { - value: 'kakao', - title: 'Kakao Cloud Platform', - imgUrl: 'https://i.pinimg.com/474x/63/43/0d/63430dc35b9b01336ecf35584bd4b7e5.jpg', - }, ]; const SidebarPaper = styled(Paper)(({ theme }) => ({ @@ -43,15 +38,15 @@ export default () => { > - - - - } - /> + {/* */} + {/* */} + {/* */} + {/* } */} + {/* /> */} Date: Fri, 29 Nov 2024 05:50:51 +0900 Subject: [PATCH 047/124] =?UTF-8?q?=F0=9F=90=9E=20Fix(server):=20=EB=AA=A8?= =?UTF-8?q?=EB=85=B8=EB=A0=88=ED=8F=AC=EC=97=90=EC=84=9C=20=ED=8C=A8?= =?UTF-8?q?=ED=82=A4=EC=A7=80=EB=A5=BC=20=EB=B6=88=EB=9F=AC=EC=98=AC=20?= =?UTF-8?q?=EB=95=8C=20=EB=8F=84=EC=BB=A4=20=EC=9D=B4=EB=AF=B8=EC=A7=80?= =?UTF-8?q?=EA=B0=80=20=EC=A0=9C=EB=8C=80=EB=A1=9C=20=EB=A7=8C=EB=93=A4?= =?UTF-8?q?=EC=96=B4=EC=A7=80=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EB=AC=B8?= =?UTF-8?q?=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .dockerignore | 8 +- apps/client/mocks.ts | 272 +++++++++--------- apps/client/src/App.tsx | 2 +- .../src/contexts/GroupContext/index.tsx | 2 +- .../client/src/contexts/NodeContext/index.tsx | 2 +- apps/hub/Dockerfile | 19 ++ apps/hub/package.json | 2 +- apps/server/Dockerfile | 192 +++++++++++-- apps/server/package.json | 2 +- apps/server/prisma/seed.js | 2 +- docker-composes/cloud-canvas-local.yml | 28 +- 11 files changed, 350 insertions(+), 181 deletions(-) create mode 100644 apps/hub/Dockerfile diff --git a/.dockerignore b/.dockerignore index 91599c40..f017a928 100644 --- a/.dockerignore +++ b/.dockerignore @@ -6,4 +6,10 @@ apps/server/.gitignore apps/client/Dockerfile apps/client/node_modules apps/client/test -apps/server/mocks +apps/client/mocks + +apps/hub/Dockerfile +apps/hub/node_modules +apps/hub/.env + +node_modules \ No newline at end of file diff --git a/apps/client/mocks.ts b/apps/client/mocks.ts index a58dfd0e..c107328e 100644 --- a/apps/client/mocks.ts +++ b/apps/client/mocks.ts @@ -1,145 +1,145 @@ -import { Group, Node } from '@types'; -import { nanoid } from 'nanoid'; +// import { Group, Node } from '@types'; +// import { nanoid } from 'nanoid'; -const CloudFunctionNode: Node = { - id: `node-${nanoid()}`, - type: 'cloud-function', - name: 'CloudFunction1', - point: { x: 270, y: 270 }, - size: { - '2d': { width: 90, height: 90 }, - '3d': { width: 96, height: 113.438, offset: 10 }, - }, - properties: { - vpc: '', - subnet: '', - region: '', - }, - connectors: {}, -}; -const ObjectStorageNode: Node = { - id: `node-${nanoid()}`, - type: 'object-storage', - name: 'ObjectStorage1', - point: { x: 100, y: 0 }, - size: { - '2d': { width: 90, height: 90 }, - '3d': { width: 100.626, height: 115.695, offset: 20 }, - }, - properties: { - vpc: '', - subnet: '', - region: '', - }, - connectors: {}, -}; -const MySQLDBNode: Node = { - id: `node-${nanoid()}`, - type: 'db-mysql', - name: 'MySQLDB1', - point: { x: 0, y: 0 }, - size: { - '2d': { width: 90, height: 90 }, - '3d': { width: 128, height: 137.5 }, - }, - properties: { - vpc: '', - subnet: '', - region: '', - }, - connectors: {}, -}; -const ServerNode: Node = { - id: `node-${nanoid()}`, - type: 'server', - name: 'WebServer1', - point: { x: 90, y: 90 }, - size: { - '2d': { width: 90, height: 90 }, - '3d': { width: 128, height: 111 }, - }, - properties: { - vpc: '', - subnet: '', - region: '', - }, - connectors: {}, -}; +// const CloudFunctionNode: Node = { +// id: `node-${nanoid()}`, +// type: 'cloud-function', +// name: 'CloudFunction1', +// point: { x: 270, y: 270 }, +// size: { +// '2d': { width: 90, height: 90 }, +// '3d': { width: 96, height: 113.438, offset: 10 }, +// }, +// properties: { +// vpc: '', +// subnet: '', +// region: '', +// }, +// connectors: {}, +// }; +// const ObjectStorageNode: Node = { +// id: `node-${nanoid()}`, +// type: 'object-storage', +// name: 'ObjectStorage1', +// point: { x: 100, y: 0 }, +// size: { +// '2d': { width: 90, height: 90 }, +// '3d': { width: 100.626, height: 115.695, offset: 20 }, +// }, +// properties: { +// vpc: '', +// subnet: '', +// region: '', +// }, +// connectors: {}, +// }; +// const MySQLDBNode: Node = { +// id: `node-${nanoid()}`, +// type: 'db-mysql', +// name: 'MySQLDB1', +// point: { x: 0, y: 0 }, +// size: { +// '2d': { width: 90, height: 90 }, +// '3d': { width: 128, height: 137.5 }, +// }, +// properties: { +// vpc: '', +// subnet: '', +// region: '', +// }, +// connectors: {}, +// }; +// const ServerNode: Node = { +// id: `node-${nanoid()}`, +// type: 'server', +// name: 'WebServer1', +// point: { x: 90, y: 90 }, +// size: { +// '2d': { width: 90, height: 90 }, +// '3d': { width: 128, height: 111 }, +// }, +// properties: { +// vpc: '', +// subnet: '', +// region: '', +// }, +// connectors: {}, +// }; -const ServerNode2: Node = { - id: `node-${nanoid()}`, - type: 'server', - name: 'WebServer2', - point: { x: 90, y: 90 }, - size: { - '2d': { width: 90, height: 90 }, - '3d': { width: 128, height: 111 }, - }, - properties: { - vpc: '', - subnet: '', - region: '', - }, - connectors: {}, -}; +// const ServerNode2: Node = { +// id: `node-${nanoid()}`, +// type: 'server', +// name: 'WebServer2', +// point: { x: 90, y: 90 }, +// size: { +// '2d': { width: 90, height: 90 }, +// '3d': { width: 128, height: 111 }, +// }, +// properties: { +// vpc: '', +// subnet: '', +// region: '', +// }, +// connectors: {}, +// }; -const SubnetGroup: Group = { - id: 'subnet1', - type: 'subnet', - name: 'Subnet-1', - nodeIds: [ServerNode.id, MySQLDBNode.id, ObjectStorageNode.id], - properties: { - cidr: '', - }, - childGroupIds: [], -}; +// const SubnetGroup: Group = { +// id: 'subnet1', +// type: 'subnet', +// name: 'Subnet-1', +// nodeIds: [ServerNode.id, MySQLDBNode.id, ObjectStorageNode.id], +// properties: { +// cidr: '', +// }, +// childGroupIds: [], +// }; -const VpcGroup: Group = { - id: 'vpc1', - type: 'vpc', - name: 'VPC-1', - nodeIds: [CloudFunctionNode.id], - properties: { - cidr: '', - }, - childGroupIds: [SubnetGroup.id], -}; +// const VpcGroup: Group = { +// id: 'vpc1', +// type: 'vpc', +// name: 'VPC-1', +// nodeIds: [CloudFunctionNode.id], +// properties: { +// cidr: '', +// }, +// childGroupIds: [SubnetGroup.id], +// }; -const RegionGroup: Group = { - id: 'seoul', - type: 'region', - name: 'region', - nodeIds: [], - properties: { - regionCode: 'KR-1', - }, - childGroupIds: [VpcGroup.id], -}; +// const RegionGroup: Group = { +// id: 'seoul', +// type: 'region', +// name: 'region', +// nodeIds: [], +// properties: { +// regionCode: 'KR-1', +// }, +// childGroupIds: [VpcGroup.id], +// }; -const mockNodes = [ - ServerNode, - CloudFunctionNode, - MySQLDBNode, - ObjectStorageNode, -]; +// const mockNodes = [ +// ServerNode, +// CloudFunctionNode, +// MySQLDBNode, +// ObjectStorageNode, +// ]; -const mockGroups = [RegionGroup, VpcGroup, SubnetGroup]; +// const mockGroups = [RegionGroup, VpcGroup, SubnetGroup]; -mockGroups.forEach((group) => { - // set properties for each group - group.nodeIds.forEach((nodeId: string) => { - const node = mockNodes.find((n) => n.id === nodeId); - if (node) { - node.properties[group.type] = group.id; - } - }); -}); +// mockGroups.forEach((group) => { +// // set properties for each group +// group.nodeIds.forEach((nodeId: string) => { +// const node = mockNodes.find((n) => n.id === nodeId); +// if (node) { +// node.properties[group.type] = group.id; +// } +// }); +// }); -export const mockInitialState = { - nodes: mockNodes.reduce((acc, node) => ({ ...acc, [node.id]: node }), {}), - groups: mockGroups.reduce( - (acc, group) => ({ ...acc, [group.id]: group }), - {}, - ), - edges: {}, -}; +// export const mockInitialState = { +// nodes: mockNodes.reduce((acc, node) => ({ ...acc, [node.id]: node }), {}), +// groups: mockGroups.reduce( +// (acc, group) => ({ ...acc, [group.id]: group }), +// {}, +// ), +// edges: {}, +// }; diff --git a/apps/client/src/App.tsx b/apps/client/src/App.tsx index d069c7ef..fc720749 100644 --- a/apps/client/src/App.tsx +++ b/apps/client/src/App.tsx @@ -1,5 +1,5 @@ import CloudGraph from '@/src/CloudGraph'; -import ErrorBoundary from '@components/ErrorBoundary'; +// import ErrorBoundary from '@components/ErrorBoundary'; import Header from '@components/Layout/Header'; import Sidebar from '@components/Layout/Sidebar'; import NetworksBar from '@components/NCloud/NetworksBar/index'; diff --git a/apps/client/src/contexts/GroupContext/index.tsx b/apps/client/src/contexts/GroupContext/index.tsx index 986bdb6b..cc847d11 100644 --- a/apps/client/src/contexts/GroupContext/index.tsx +++ b/apps/client/src/contexts/GroupContext/index.tsx @@ -1,4 +1,4 @@ -import { mockInitialState } from '../../../mocks'; +// import { mockInitialState } from '../../../mocks'; import { GroupAction, groupReducer, diff --git a/apps/client/src/contexts/NodeContext/index.tsx b/apps/client/src/contexts/NodeContext/index.tsx index 4512c4cf..429f3104 100644 --- a/apps/client/src/contexts/NodeContext/index.tsx +++ b/apps/client/src/contexts/NodeContext/index.tsx @@ -1,4 +1,4 @@ -import { mockInitialState } from '../../../mocks'; +// import { mockInitialState } from '../../../mocks'; import { NodeAction, nodeReducer, diff --git a/apps/hub/Dockerfile b/apps/hub/Dockerfile new file mode 100644 index 00000000..c4617690 --- /dev/null +++ b/apps/hub/Dockerfile @@ -0,0 +1,19 @@ +FROM node:20 AS development +WORKDIR /development +COPY ./pnpm-lock.yaml ./apps/hub/package*.json . +RUN npm install -g pnpm && pnpm install + +FROM node:20 AS build +WORKDIR /build +COPY --from=development /development/node_modules/ ./node_modules +COPY ./apps/hub/ . +RUN npm run build + +FROM node:20-alpine AS production +WORKDIR /app +RUN mkdir -p /app/server +COPY --from=build /build/.next/standalone/ ./standalone/ +COPY --from=build /build/.next/static/ ./standalone/.next/ + +ENV NEXT_PUBLIC_BACK_URL=http://localhost:3000 +ENTRYPOINT ["sh", "-c", "node standalone/server.js"] \ No newline at end of file diff --git a/apps/hub/package.json b/apps/hub/package.json index 9ba2b0df..d019b5b7 100644 --- a/apps/hub/package.json +++ b/apps/hub/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "next dev -p 3001", + "dev": "next dev", "build": "next build", "start": "next start", "lint": "next lint" diff --git a/apps/server/Dockerfile b/apps/server/Dockerfile index 9227f94a..0a4989a6 100644 --- a/apps/server/Dockerfile +++ b/apps/server/Dockerfile @@ -1,28 +1,170 @@ -FROM node:20 AS development -WORKDIR /development -COPY ./pnpm-lock.yaml ./apps/server/package*.json . -RUN npm install -g pnpm && pnpm install - -FROM node:20 AS build -WORKDIR /build -RUN mkdir -p /build/server && npm install -g pnpm -COPY --from=development /development/node_modules/ /build/server/node_modules -COPY --from=development /development/pnpm-lock.yaml /build/server/ -COPY ./apps/server/ /build/server/ -WORKDIR /build/server -RUN npx prisma generate && npm run build && rm -rf node_modules && pnpm install --frozen-lockfile --prod - -FROM node:20-alpine AS production -WORKDIR /app -RUN apk add --no-cache openssl -COPY ./apps/server/package.json . -COPY --from=build /build/server/node_modules ./node_modules -COPY --from=build /build/server/dist ./dist -RUN mkdir -p /app/prisma -COPY ./apps/server/prisma/ /app/prisma/ - -ENV DATABASE_URL=mysql://johndoe:randompassword@localhost:3306/mydb +# FROM node:20 AS development +# WORKDIR /development +# COPY . . +# RUN npm install -g pnpm && pnpm install +# RUN pnpm build + +# FROM node:20 AS build +# WORKDIR /build +# RUN mkdir -p /build/server && mkdir -p /build/packages && npm install -g pnpm +# COPY --from=development /development/apps/server/ /build/server/ +# COPY --from=development /development/pnpm-lock.yaml . +# COPY --from=development /development/packages/ ./packages/ +# COPY --from=development /development/pnpm-workspace.yaml . +# WORKDIR /build/server +# RUN rm -rf node_modules && pnpm install --prod + +# FROM node:20-alpine AS production +# WORKDIR /app +# RUN apk add --no-cache openssl +# RUN mkdir -p /app/node_modules +# COPY ./apps/server/package.json . +# COPY --from=build /build/server/node_modules ./node_modules +# COPY --from=build /build/server/dist ./dist +# RUN mkdir -p /app/prisma +# COPY ./apps/server/prisma/ /app/prisma/ + +# 빌드 단계: 애플리케이션 준비 및 프로덕션 의존성 설치 +# FROM node:20 AS build +# WORKDIR /build + +# pnpm을 전역으로 설치 +# RUN npm install -g pnpm + +# # 개발 단계에서 파일 복사 +# COPY --from=development /development/apps/server/ /build/server/ +# COPY --from=development /development/pnpm-lock.yaml . +# COPY --from=development /development/packages/ ./packages/ +# COPY --from=development /development/pnpm-workspace.yaml . + +# 프로덕션 의존성만 설치 +# WORKDIR /build/server +# RUN pnpm install + +# 프로덕션 단계: 런타임 환경 설정 +# FROM node:20-alpine AS production +# WORKDIR /app + +# # 필요한 시스템 의존성 설치 (Prisma 등) +# RUN apk add --no-cache openssl + +# # 필요한 디렉토리 생성 +# RUN mkdir -p /app/node_modules /app/prisma + +# # 빌드 단계에서 애플리케이션 파일과 빌드 출력 복사 +# COPY ./apps/server/package.json . +# COPY --from=build /build/server/node_modules ./node_modules +# COPY --from=build /build/server ./server +# COPY ./apps/server/prisma/ /app/prisma/ + +# # 프로덕션 환경 변수 설정 +# ENV DATABASE_URL=mysql://johndoe:randompassword@localhost:3306/mydb +# ENV PORT=3000 +# ENV NODE_ENV=production +# EXPOSE 3000 + +# # 애플리케이션 시작 +# ENTRYPOINT ["sh", "-c", "node ./server/dist/src/main.js"] + + +# ENV DATABASE_URL=mysql://johndoe:randompassword@localhost:3306/mydb +# ENV PORT=3000 +# ENV NODE_ENV=development +# EXPOSE 3000 +# ENTRYPOINT ["sh", "-c", "npx prisma generate && node ./dist/src/main.js"] +# # Development stage: Install dependencies and build the project +# FROM node:20 AS development +# WORKDIR /development + +# FROM node:20 AS development +# WORKDIR /development +# COPY . . +# RUN npm install -g pnpm && pnpm install +# RUN pnpm build + +# # Build stage: Prepare production build +# FROM node:20 AS build +# WORKDIR /build + +# # Install pnpm and prepare the workspace +# RUN npm install -g pnpm +# COPY --from=development /development/apps/server/package.json ./apps/server/ +# COPY --from=development /development/pnpm-workspace.yaml ./ +# COPY --from=development /development/pnpm-lock.yaml ./ +# COPY --from=development /development/apps/server/ ./apps/server/ +# COPY --from=development /development/packages/ ./packages/ + +# # Install production-only dependencies +# WORKDIR /build/apps/server +# RUN pnpm install --frozen-lockfile --prod + +# # Production stage: Setup runtime environment +# FROM node:20-alpine AS production +# WORKDIR /app + +# # Install required system packages +# RUN apk add --no-cache openssl + +# # Copy necessary files for the application +# COPY ./apps/server/package.json . +# COPY --from=build /build/apps/server/node_modules ./node_modules +# COPY --from=build /build/apps/server/dist ./dist +# RUN mkdir -p /app/prisma +# COPY ./apps/server/prisma/ /app/prisma/ + +# # Environment variables and entrypoint +# ENV DATABASE_URL=mysql://johndoe:randompassword@localhost:3306/mydb +# ENV PORT=3000 +# ENV NODE_ENV=production +# EXPOSE 3000 + +# FROM node:20 AS mono +# WORKDIR /mono +# COPY . . +# RUN npm install -g pnpm && pnpm install && pnpm build + +# FROM node:20 AS development +# WORKDIR /development +# COPY ./packages/ ./packages/ +# COPY ./pnpm-lock.yaml ./apps/server/package*.json ./pnpm-workspace.yaml . +# RUN npm install -g pnpm && pnpm install --prod + +# FROM node:20-alpine AS production +# WORKDIR /app +# RUN apk add --no-cache openssl +# COPY ./apps/server/package.json ./apps/server/ +# COPY --from=development /development/node_modules ./apps/server/node_modules +# COPY --from=mono /mono/apps/server/dist ./apps/server/dist +# RUN mkdir -p /app/packages +# COPY --from=mono /mono/packages /app/packages +# COPY ./apps/server/prisma/ ./apps/server/prisma/ +# COPY ./pnpm-workspace.yaml . + +# ENV DATABASE_URL=mysql://johndoe:randompassword@localhost:3306/mydb +# ENV PORT=3000 +# ENV NODE_ENV=development +# EXPOSE 3000 +# ENTRYPOINT ["sh", "-c", "node apps/server/dist/src/main.js"] + +FROM node:20 + +WORKDIR /usr/src/app + +COPY . . + +RUN npm install -g pnpm && pnpm install && pnpm build + +# Copy app source + +ENV DATABASE_URL=mysql://seogeonhyuk:rhdrhdCLF@192.168.64.3:3306/cloud_canvas +ENV MYSQL_HOST=1 +ENV MYSQL_PORT=3306 +ENV REDIS_HOST=3 +ENV REDIS_PORT=6379 +ENV NCLOUD_ACCESS_KEY=0 +ENV NCLOUD_SECRET_KEY=0 ENV PORT=3000 ENV NODE_ENV=development EXPOSE 3000 -ENTRYPOINT ["sh", "-c", "npx prisma generate && node ./dist/src/main.js"] \ No newline at end of file + +CMD [ "node", "apps/server/dist/src/main.js" ] \ No newline at end of file diff --git a/apps/server/package.json b/apps/server/package.json index 433c23ee..b57dd49a 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -6,7 +6,7 @@ "private": true, "license": "UNLICENSED", "scripts": { - "build": "nest build", + "build": "pnpm prisma-generate && nest build", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "start": "nest start", "prisma-generate": "prisma generate", diff --git a/apps/server/prisma/seed.js b/apps/server/prisma/seed.js index ba173c62..d782a5cd 100644 --- a/apps/server/prisma/seed.js +++ b/apps/server/prisma/seed.js @@ -118,7 +118,7 @@ async function main() { const tags = []; for (let i = 0; i < tagAmount; i++) { const tag = { - name: faker.word.verb({ length: { max: 15 } }), + name: faker.word.verb({ length: { max: 10 } }), }; tags.push(tag); } diff --git a/docker-composes/cloud-canvas-local.yml b/docker-composes/cloud-canvas-local.yml index ef19377b..0f4e5b37 100644 --- a/docker-composes/cloud-canvas-local.yml +++ b/docker-composes/cloud-canvas-local.yml @@ -65,6 +65,8 @@ services: container_name: back environment: DATABASE_URL: mysql://cloud_canvas_user:password@mysql:3306/cloud_canvas + NCLOUD_ACCESS_KEY: ${NCLOUD_ACCESS_KEY} + NCLOUD_SECRET_KEY: ${NCLOUD_SECRET_KEY} ports: - '3000:3000' depends_on: @@ -72,27 +74,27 @@ services: condition: service_healthy redis: condition: service_healthy - entrypoint: sh -c "npx prisma migrate deploy && npx prisma db seed && node ./dist/src/main.js" + entrypoint: sh -c "cd apps/server && npx prisma migrate dev && npx prisma db seed && node ./dist/src/main.js" networks: - cloud-canvas-network restart: unless-stopped - front: - build: - context: ../ - dockerfile: apps/client/Dockerfile - container_name: front - ports: - - '5001:5000' - networks: - - cloud-canvas-network - restart: unless-stopped + # front: + # build: + # context: ../ + # dockerfile: apps/client/Dockerfile + # container_name: front + # ports: + # - '5001:5000' + # networks: + # - cloud-canvas-network + # restart: unless-stopped fluentd: - build: ./logging/fluentd/ + build: ./monitorin/logging/fluentd/ container_name: fluentd volumes: - - ./logging/fluentd/conf/fluent.conf:/fluentd/etc/fluent.conf + - ./monitoring/logging/fluentd/conf/fluent.conf:/fluentd/etc/fluent.conf ports: - '24224:24224' - '24224:24224/udp' From e1904fad83c47ad6478a130d69ab270dbd3a772f Mon Sep 17 00:00:00 2001 From: Geonhyuk Seo Date: Fri, 29 Nov 2024 05:55:34 +0900 Subject: [PATCH 048/124] =?UTF-8?q?=F0=9F=90=9E=20Fix(.github):=20github?= =?UTF-8?q?=20action=20cache=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/pr-build-test.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/pr-build-test.yml b/.github/workflows/pr-build-test.yml index 27c5763d..46d28aea 100644 --- a/.github/workflows/pr-build-test.yml +++ b/.github/workflows/pr-build-test.yml @@ -22,7 +22,6 @@ jobs: with: version: 9.12.3 node-version: 20 - cache: true - name: Install dependencies with pnpm run: pnpm install From f03b47b50edf28e8300aa6cd055f850e65bc9483 Mon Sep 17 00:00:00 2001 From: Geonhyuk Seo Date: Fri, 29 Nov 2024 05:59:10 +0900 Subject: [PATCH 049/124] =?UTF-8?q?=F0=9F=90=9E=20Fix(.github):=20?= =?UTF-8?q?=EA=B9=83=ED=97=88=EB=B8=8C=20=EC=95=A1=EC=85=98=20no=20frozen?= =?UTF-8?q?=20lockfile=20=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/pr-build-test.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pr-build-test.yml b/.github/workflows/pr-build-test.yml index 46d28aea..afe7973c 100644 --- a/.github/workflows/pr-build-test.yml +++ b/.github/workflows/pr-build-test.yml @@ -22,9 +22,10 @@ jobs: with: version: 9.12.3 node-version: 20 + cache: true - name: Install dependencies with pnpm - run: pnpm install + run: pnpm install --no-frozen-lockfile - name: Build the project run: pnpm build From 2b6430c36accf94705498b93f0b3970073aa2d8c Mon Sep 17 00:00:00 2001 From: p1n9d3v Date: Fri, 29 Nov 2024 06:07:35 +0900 Subject: [PATCH 050/124] =?UTF-8?q?=F0=9F=90=9E=20Fix(client):=20=EA=B7=B8?= =?UTF-8?q?=EB=A3=B9=20=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/Group/ncloud/RegionGroup.tsx | 8 +-- .../components/Group/ncloud/SubnetGroup.tsx | 49 ++++++++++++++----- .../src/components/Group/ncloud/Title.tsx | 2 +- .../src/components/Group/ncloud/VPCGroup.tsx | 49 ++++++++++++++----- apps/client/src/utils/index.ts | 6 +-- 5 files changed, 80 insertions(+), 34 deletions(-) diff --git a/apps/client/src/components/Group/ncloud/RegionGroup.tsx b/apps/client/src/components/Group/ncloud/RegionGroup.tsx index 0e56d74b..2d222447 100644 --- a/apps/client/src/components/Group/ncloud/RegionGroup.tsx +++ b/apps/client/src/components/Group/ncloud/RegionGroup.tsx @@ -19,19 +19,19 @@ const Region3D = ({ bounds, properties, color }: Props) => { const bottomLeftGrid = screenToGrid2d({ x: 0, y: bounds.height }); const point1 = gridToScreen3d({ - col: topLeftGrid.col, + col: topLeftGrid.col + 1, row: topLeftGrid.row, }); const point2 = gridToScreen3d({ - col: topRightGrid.col, + col: topRightGrid.col + 1, row: topRightGrid.row, }); const point3 = gridToScreen3d({ - col: bottomRightGrid.col, + col: bottomRightGrid.col + 1, row: bottomRightGrid.row, }); const point4 = gridToScreen3d({ - col: bottomLeftGrid.col, + col: bottomLeftGrid.col + 1, row: bottomLeftGrid.row, }); diff --git a/apps/client/src/components/Group/ncloud/SubnetGroup.tsx b/apps/client/src/components/Group/ncloud/SubnetGroup.tsx index b04e9802..2d222447 100644 --- a/apps/client/src/components/Group/ncloud/SubnetGroup.tsx +++ b/apps/client/src/components/Group/ncloud/SubnetGroup.tsx @@ -1,7 +1,7 @@ import Text from '@components/Group/ncloud/Title'; import { useDimensionContext } from '@contexts/DimensionContext'; import { Bounds, Group } from '@types'; -import { generateRandomRGB } from '@utils'; +import { generateRandomRGB, gridToScreen3d, screenToGrid2d } from '@utils'; import { useMemo } from 'react'; interface Props extends Partial { @@ -9,13 +9,39 @@ interface Props extends Partial { bounds: Bounds; } -const Subnet3D = ({ bounds, properties, color }: Props) => { - const isoMatrix = new DOMMatrix() - .rotate(30) - .skewX(-30) - .scale(1, 0.8602) - .translate(0, 0); - const points = `0 0, 0 ${bounds.height}, ${bounds.width} ${bounds.height}, ${bounds.width} 0`; +const Region3D = ({ bounds, properties, color }: Props) => { + const topLeftGrid = screenToGrid2d({ x: 0, y: 0 }); + const topRightGrid = screenToGrid2d({ x: bounds.width, y: 0 }); + const bottomRightGrid = screenToGrid2d({ + x: bounds.width, + y: bounds.height, + }); + const bottomLeftGrid = screenToGrid2d({ x: 0, y: bounds.height }); + + const point1 = gridToScreen3d({ + col: topLeftGrid.col + 1, + row: topLeftGrid.row, + }); + const point2 = gridToScreen3d({ + col: topRightGrid.col + 1, + row: topRightGrid.row, + }); + const point3 = gridToScreen3d({ + col: bottomRightGrid.col + 1, + row: bottomRightGrid.row, + }); + const point4 = gridToScreen3d({ + col: bottomLeftGrid.col + 1, + row: bottomLeftGrid.row, + }); + + const points = ` + ${point1.x} ${point1.y}, + ${point2.x} ${point2.y}, + ${point3.x} ${point3.y}, + ${point4.x} ${point4.y} + `; + return ( <> { stroke={color} strokeWidth="8" fill="none" - transform={isoMatrix.toString()} > ); }; -const Subnet2D = ({ bounds, color, properties }: Props) => { +const Region2D = ({ bounds, color, properties }: Props) => { const points = `0 0, 0 ${bounds.height}, ${bounds.width} ${bounds.height}, ${bounds.width} 0`; return ( @@ -51,8 +76,8 @@ export default ({ bounds, properties }: Omit) => { const color = useMemo(() => generateRandomRGB(), []); return dimension === '2d' ? ( - + ) : ( - + ); }; diff --git a/apps/client/src/components/Group/ncloud/Title.tsx b/apps/client/src/components/Group/ncloud/Title.tsx index f76a5310..6ba19d10 100644 --- a/apps/client/src/components/Group/ncloud/Title.tsx +++ b/apps/client/src/components/Group/ncloud/Title.tsx @@ -24,7 +24,7 @@ export default ({ bounds, color, text }: Props) => { const rectWidth = fontSize * textLength; const rectHeight = 50; const rectY = 10; - const rectX = 10; + const rectX = dimension === '2d' ? 10 : 90; const matrix = convertToIsoMatrix(bounds.x, bounds.y).toString(); const centerX = rectWidth / 2 + rectX; diff --git a/apps/client/src/components/Group/ncloud/VPCGroup.tsx b/apps/client/src/components/Group/ncloud/VPCGroup.tsx index 12126cdf..2d222447 100644 --- a/apps/client/src/components/Group/ncloud/VPCGroup.tsx +++ b/apps/client/src/components/Group/ncloud/VPCGroup.tsx @@ -1,7 +1,7 @@ import Text from '@components/Group/ncloud/Title'; import { useDimensionContext } from '@contexts/DimensionContext'; import { Bounds, Group } from '@types'; -import { generateRandomRGB } from '@utils'; +import { generateRandomRGB, gridToScreen3d, screenToGrid2d } from '@utils'; import { useMemo } from 'react'; interface Props extends Partial { @@ -9,13 +9,39 @@ interface Props extends Partial { bounds: Bounds; } -const VPC3D = ({ bounds, properties, color }: Props) => { - const isoMatrix = new DOMMatrix() - .rotate(30) - .skewX(-30) - .scale(1, 0.8602) - .translate(0, 0); - const points = `0 0, 0 ${bounds.height}, ${bounds.width} ${bounds.height}, ${bounds.width} 0`; +const Region3D = ({ bounds, properties, color }: Props) => { + const topLeftGrid = screenToGrid2d({ x: 0, y: 0 }); + const topRightGrid = screenToGrid2d({ x: bounds.width, y: 0 }); + const bottomRightGrid = screenToGrid2d({ + x: bounds.width, + y: bounds.height, + }); + const bottomLeftGrid = screenToGrid2d({ x: 0, y: bounds.height }); + + const point1 = gridToScreen3d({ + col: topLeftGrid.col + 1, + row: topLeftGrid.row, + }); + const point2 = gridToScreen3d({ + col: topRightGrid.col + 1, + row: topRightGrid.row, + }); + const point3 = gridToScreen3d({ + col: bottomRightGrid.col + 1, + row: bottomRightGrid.row, + }); + const point4 = gridToScreen3d({ + col: bottomLeftGrid.col + 1, + row: bottomLeftGrid.row, + }); + + const points = ` + ${point1.x} ${point1.y}, + ${point2.x} ${point2.y}, + ${point3.x} ${point3.y}, + ${point4.x} ${point4.y} + `; + return ( <> { stroke={color} strokeWidth="8" fill="none" - transform={isoMatrix.toString()} > ); }; -const VPC2D = ({ bounds, color, properties }: Props) => { +const Region2D = ({ bounds, color, properties }: Props) => { const points = `0 0, 0 ${bounds.height}, ${bounds.width} ${bounds.height}, ${bounds.width} 0`; return ( @@ -51,8 +76,8 @@ export default ({ bounds, properties }: Omit) => { const color = useMemo(() => generateRandomRGB(), []); return dimension === '2d' ? ( - + ) : ( - + ); }; diff --git a/apps/client/src/utils/index.ts b/apps/client/src/utils/index.ts index a694e60f..e0a1638e 100644 --- a/apps/client/src/utils/index.ts +++ b/apps/client/src/utils/index.ts @@ -81,11 +81,7 @@ export const convert3dTo2dPoint = (point: Point) => { }; export const convert2dTo3dPoint = (point: Point) => { - const grid = screenToGrid2d(point); - return gridToScreen3d({ - col: grid.col + 1, - row: grid.row, - }); + return gridToScreen3d(screenToGrid2d(point)); }; export const generateRandomRGB = () => { From 75712b0c9a91fd506eb7061e3dc68bf1d6d21312 Mon Sep 17 00:00:00 2001 From: p1n9d3v Date: Fri, 29 Nov 2024 06:16:22 +0900 Subject: [PATCH 051/124] =?UTF-8?q?=F0=9F=90=9E=20Fix(client):=20terraform?= =?UTF-8?q?=20=EC=9D=B4=EB=A6=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/Layout/Header/index.tsx | 7 +- pnpm-lock.yaml | 214 +++++++++++++++++- 2 files changed, 208 insertions(+), 13 deletions(-) diff --git a/apps/client/src/components/Layout/Header/index.tsx b/apps/client/src/components/Layout/Header/index.tsx index fba89b78..ce8bdf09 100644 --- a/apps/client/src/components/Layout/Header/index.tsx +++ b/apps/client/src/components/Layout/Header/index.tsx @@ -16,7 +16,7 @@ import IconButton from '@mui/material/IconButton'; import Stack from '@mui/material/Stack'; import { styled, useColorScheme } from '@mui/material/styles'; import Typography from '@mui/material/Typography'; -import { TerraformConvertor } from 'node_modules/terraform/convertor/TerraformConvertor'; +import { TerraformConverter } from 'node_modules/terraform/converter/TerraformConverter'; import { useState } from 'react'; const StyledBox = styled(Box)(({ theme }) => ({ @@ -65,8 +65,9 @@ export default () => { return; } - const Converter = new TerraformConvertor(); - Converter.addResourceFromJson(nodeProperties); + const Converter = new TerraformConverter(); + console.log(nodeProperties); + Converter.addResourceFromJson([nodeProperties]); setTerraformCode(Converter.generate()); setOpenDrawer(true); }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6045e56f..7503885b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -56,6 +56,15 @@ importers: react-dom: specifier: ^18.3.1 version: 18.3.1(react@18.3.1) + react-syntax-highlighter: + specifier: ^15.6.1 + version: 15.6.1(react@18.3.1) + react-type-animation: + specifier: ^3.2.0 + version: 3.2.0(prop-types@15.8.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + terraform: + specifier: file:../../packages/terraform + version: link:../../packages/terraform devDependencies: '@types/react': specifier: ^18.3.11 @@ -63,6 +72,9 @@ importers: '@types/react-dom': specifier: ^18.3.1 version: 18.3.1 + '@types/react-syntax-highlighter': + specifier: ^15.5.13 + version: 15.5.13 '@vitejs/plugin-react': specifier: ^4.3.3 version: 4.3.3(vite@5.4.11(@types/node@22.9.0)(terser@5.36.0)) @@ -129,7 +141,7 @@ importers: version: 9.2.0 '@nestjs-modules/ioredis': specifier: ^2.0.2 - version: 2.0.2(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(@prisma/client@5.22.0(prisma@5.22.0))(ioredis@5.4.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + version: 2.0.2(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@prisma/client@5.22.0(prisma@5.22.0))(ioredis@5.4.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/common': specifier: ^10.0.0 version: 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) @@ -153,10 +165,10 @@ importers: version: 10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7) '@nestjs/schedule': specifier: ^4.1.1 - version: 4.1.1(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7) + version: 4.1.1(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1)) '@nestjs/swagger': specifier: ^8.0.5 - version: 8.0.5(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2) + version: 8.0.5(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2) '@prisma/client': specifier: ^5.22.0 version: 5.22.0(prisma@5.22.0) @@ -196,7 +208,7 @@ importers: version: 10.2.3(chokidar@3.6.0)(typescript@5.6.3) '@nestjs/testing': specifier: ^10.0.0 - version: 10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(@nestjs/platform-express@10.4.7) + version: 10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)) '@swc/core': specifier: ^1.9.1 version: 1.9.2(@swc/helpers@0.5.13) @@ -2065,6 +2077,9 @@ packages: '@types/graceful-fs@4.1.9': resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} + '@types/hast@2.3.10': + resolution: {integrity: sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==} + '@types/http-errors@2.0.4': resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==} @@ -2131,6 +2146,9 @@ packages: '@types/react-dom@18.3.1': resolution: {integrity: sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==} + '@types/react-syntax-highlighter@15.5.13': + resolution: {integrity: sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==} + '@types/react-transition-group@4.4.11': resolution: {integrity: sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA==} @@ -2155,6 +2173,9 @@ packages: '@types/supertest@6.0.2': resolution: {integrity: sha512-137ypx2lk/wTQbW6An6safu9hXmajAifU/s7szAHLN/FeIm5w7yR0Wkl9fdJMRSHwOn4HLAI0DaB2TOORuhPDg==} + '@types/unist@2.0.11': + resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} + '@types/validator@13.12.2': resolution: {integrity: sha512-6SlHBzUW8Jhf3liqrGGXyTJSIFe4nqlJ5A5KaMZ2l/vbM3Wh3KSybots/wfWVzNLK4D1NZluDlSQIbIEPx6oyA==} @@ -2694,6 +2715,15 @@ packages: resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} engines: {node: '>=10'} + character-entities-legacy@1.1.4: + resolution: {integrity: sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==} + + character-entities@1.2.4: + resolution: {integrity: sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==} + + character-reference-invalid@1.1.4: + resolution: {integrity: sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==} + chardet@0.7.0: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} @@ -2830,6 +2860,9 @@ packages: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} + comma-separated-tokens@1.0.8: + resolution: {integrity: sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==} + commander@12.1.0: resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} engines: {node: '>=18'} @@ -3504,6 +3537,9 @@ packages: fastq@1.17.1: resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + fault@1.0.4: + resolution: {integrity: sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==} + fb-watchman@2.0.2: resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} @@ -3594,6 +3630,10 @@ packages: resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==} engines: {node: '>= 6'} + format@0.2.2: + resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} + engines: {node: '>=0.4.x'} + formidable@2.1.2: resolution: {integrity: sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==} @@ -3778,6 +3818,12 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + hast-util-parse-selector@2.2.5: + resolution: {integrity: sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==} + + hastscript@6.0.0: + resolution: {integrity: sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==} + helmet@8.0.0: resolution: {integrity: sha512-VyusHLEIIO5mjQPUI1wpOAEu+wl6Q0998jzTxqUYGE45xCIcAxy3MsbEK/yyJUJ3ADeMoB6MornPH6GMWAf+Pw==} engines: {node: '>=18.0.0'} @@ -3790,6 +3836,12 @@ packages: resolution: {integrity: sha512-qlspKUK7IlSQv2o+5I7yhUd7TxlOG2Vr5LTa3ve2XSNVKAL/n/u/7KLvKmFNimomDIKvZFXWHv0T12mv7rT8Aw==} engines: {node: '>=8'} + highlight.js@10.7.3: + resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} + + highlightjs-vue@1.0.0: + resolution: {integrity: sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==} + hoist-non-react-statics@3.3.2: resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} @@ -3889,6 +3941,12 @@ packages: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} + is-alphabetical@1.0.4: + resolution: {integrity: sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==} + + is-alphanumerical@1.0.4: + resolution: {integrity: sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==} + is-array-buffer@3.0.4: resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==} engines: {node: '>= 0.4'} @@ -3933,6 +3991,9 @@ packages: resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} engines: {node: '>= 0.4'} + is-decimal@1.0.4: + resolution: {integrity: sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==} + is-docker@2.2.1: resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} engines: {node: '>=8'} @@ -3978,6 +4039,9 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-hexadecimal@1.0.4: + resolution: {integrity: sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==} + is-inside-container@1.0.0: resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} engines: {node: '>=14.16'} @@ -4468,6 +4532,9 @@ packages: loupe@3.1.2: resolution: {integrity: sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==} + lowlight@1.20.0: + resolution: {integrity: sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==} + lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -4830,6 +4897,9 @@ packages: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} + parse-entities@2.0.0: + resolution: {integrity: sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==} + parse-json@5.2.0: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} @@ -5033,6 +5103,14 @@ packages: engines: {node: '>=16.13'} hasBin: true + prismjs@1.27.0: + resolution: {integrity: sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==} + engines: {node: '>=6'} + + prismjs@1.29.0: + resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==} + engines: {node: '>=6'} + process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} @@ -5043,6 +5121,9 @@ packages: prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + property-information@5.6.0: + resolution: {integrity: sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==} + proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -5106,12 +5187,24 @@ packages: resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} engines: {node: '>=0.10.0'} + react-syntax-highlighter@15.6.1: + resolution: {integrity: sha512-OqJ2/vL7lEeV5zTJyG7kmARppUjiB9h9udl4qHQjjgEos66z00Ia0OckwYfRxCSFrW8RJIBnsBwQsHZbVPspqg==} + peerDependencies: + react: '>= 0.14.0' + react-transition-group@4.4.5: resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} peerDependencies: react: '>=16.6.0' react-dom: '>=16.6.0' + react-type-animation@3.2.0: + resolution: {integrity: sha512-WXTe0i3rRNKjmggPvT5ntye1QBt0ATGbijeW6V3cQe2W0jaMABXXlPPEdtofnS9tM7wSRHchEvI9SUw+0kUohw==} + peerDependencies: + prop-types: ^15.5.4 + react: '>= 15.0.0' + react-dom: '>= 15.0.0' + react@18.3.1: resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} engines: {node: '>=0.10.0'} @@ -5157,6 +5250,9 @@ packages: resolution: {integrity: sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==} engines: {node: '>= 0.4'} + refractor@3.6.0: + resolution: {integrity: sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==} + regenerator-runtime@0.14.1: resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} @@ -5414,6 +5510,9 @@ packages: resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} engines: {node: '>= 8'} + space-separated-tokens@1.1.5: + resolution: {integrity: sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==} + spawndamnit@2.0.0: resolution: {integrity: sha512-j4JKEcncSjFlqIwU5L/rp2N5SIPsdxaRsIv678+TZxZ0SRDJTm8JrxJMjE/XuiEZNEir3S8l0Fa3Ke339WI4qA==} @@ -7358,13 +7457,13 @@ snapshots: optionalDependencies: '@types/react': 18.3.12 - '@nestjs-modules/ioredis@2.0.2(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(@prisma/client@5.22.0(prisma@5.22.0))(ioredis@5.4.1)(reflect-metadata@0.2.2)(rxjs@7.8.1)': + '@nestjs-modules/ioredis@2.0.2(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@prisma/client@5.22.0(prisma@5.22.0))(ioredis@5.4.1)(reflect-metadata@0.2.2)(rxjs@7.8.1)': dependencies: '@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/core': 10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1) ioredis: 5.4.1 optionalDependencies: - '@nestjs/terminus': 10.2.0(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(@prisma/client@5.22.0(prisma@5.22.0))(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/terminus': 10.2.0(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@prisma/client@5.22.0(prisma@5.22.0))(reflect-metadata@0.2.2)(rxjs@7.8.1) transitivePeerDependencies: - '@grpc/grpc-js' - '@grpc/proto-loader' @@ -7476,7 +7575,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@nestjs/schedule@4.1.1(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)': + '@nestjs/schedule@4.1.1(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1))': dependencies: '@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/core': 10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1) @@ -7494,7 +7593,7 @@ snapshots: transitivePeerDependencies: - chokidar - '@nestjs/swagger@8.0.5(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)': + '@nestjs/swagger@8.0.5(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)': dependencies: '@microsoft/tsdoc': 0.15.0 '@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) @@ -7509,7 +7608,7 @@ snapshots: class-transformer: 0.5.1 class-validator: 0.14.1 - '@nestjs/terminus@10.2.0(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(@prisma/client@5.22.0(prisma@5.22.0))(reflect-metadata@0.2.2)(rxjs@7.8.1)': + '@nestjs/terminus@10.2.0(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@prisma/client@5.22.0(prisma@5.22.0))(reflect-metadata@0.2.2)(rxjs@7.8.1)': dependencies: '@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/core': 10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1) @@ -7521,7 +7620,7 @@ snapshots: '@prisma/client': 5.22.0(prisma@5.22.0) optional: true - '@nestjs/testing@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(@nestjs/platform-express@10.4.7)': + '@nestjs/testing@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7))': dependencies: '@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/core': 10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1) @@ -7829,6 +7928,10 @@ snapshots: dependencies: '@types/node': 22.9.0 + '@types/hast@2.3.10': + dependencies: + '@types/unist': 2.0.11 + '@types/http-errors@2.0.4': {} '@types/istanbul-lib-coverage@2.0.6': {} @@ -7896,6 +7999,10 @@ snapshots: dependencies: '@types/react': 18.3.12 + '@types/react-syntax-highlighter@15.5.13': + dependencies: + '@types/react': 18.3.12 + '@types/react-transition-group@4.4.11': dependencies: '@types/react': 18.3.12 @@ -7932,6 +8039,8 @@ snapshots: '@types/methods': 1.1.4 '@types/superagent': 8.1.9 + '@types/unist@2.0.11': {} + '@types/validator@13.12.2': {} '@types/yargs-parser@21.0.3': {} @@ -8606,6 +8715,12 @@ snapshots: char-regex@1.0.2: {} + character-entities-legacy@1.1.4: {} + + character-entities@1.2.4: {} + + character-reference-invalid@1.1.4: {} + chardet@0.7.0: {} check-disk-space@3.4.0: @@ -8733,6 +8848,8 @@ snapshots: dependencies: delayed-stream: 1.0.0 + comma-separated-tokens@1.0.8: {} + commander@12.1.0: {} commander@2.20.3: {} @@ -9640,6 +9757,10 @@ snapshots: dependencies: reusify: 1.0.4 + fault@1.0.4: + dependencies: + format: 0.2.2 + fb-watchman@2.0.2: dependencies: bser: 2.1.1 @@ -9750,6 +9871,8 @@ snapshots: combined-stream: 1.0.8 mime-types: 2.1.35 + format@0.2.2: {} + formidable@2.1.2: dependencies: dezalgo: 1.0.4 @@ -9950,12 +10073,26 @@ snapshots: dependencies: function-bind: 1.1.2 + hast-util-parse-selector@2.2.5: {} + + hastscript@6.0.0: + dependencies: + '@types/hast': 2.3.10 + comma-separated-tokens: 1.0.8 + hast-util-parse-selector: 2.2.5 + property-information: 5.6.0 + space-separated-tokens: 1.1.5 + helmet@8.0.0: {} hexoid@1.0.0: {} hexoid@2.0.0: {} + highlight.js@10.7.3: {} + + highlightjs-vue@1.0.0: {} + hoist-non-react-statics@3.3.2: dependencies: react-is: 16.13.1 @@ -10109,6 +10246,13 @@ snapshots: ipaddr.js@1.9.1: {} + is-alphabetical@1.0.4: {} + + is-alphanumerical@1.0.4: + dependencies: + is-alphabetical: 1.0.4 + is-decimal: 1.0.4 + is-array-buffer@3.0.4: dependencies: call-bind: 1.0.7 @@ -10154,6 +10298,8 @@ snapshots: dependencies: has-tostringtag: 1.0.2 + is-decimal@1.0.4: {} + is-docker@2.2.1: {} is-docker@3.0.0: {} @@ -10184,6 +10330,8 @@ snapshots: dependencies: is-extglob: 2.1.1 + is-hexadecimal@1.0.4: {} + is-inside-container@1.0.0: dependencies: is-docker: 3.0.0 @@ -10851,6 +10999,11 @@ snapshots: loupe@3.1.2: {} + lowlight@1.20.0: + dependencies: + fault: 1.0.4 + highlight.js: 10.7.3 + lru-cache@10.4.3: {} lru-cache@4.1.5: @@ -11180,6 +11333,15 @@ snapshots: dependencies: callsites: 3.1.0 + parse-entities@2.0.0: + dependencies: + character-entities: 1.2.4 + character-entities-legacy: 1.1.4 + character-reference-invalid: 1.1.4 + is-alphanumerical: 1.0.4 + is-decimal: 1.0.4 + is-hexadecimal: 1.0.4 + parse-json@5.2.0: dependencies: '@babel/code-frame': 7.26.2 @@ -11332,6 +11494,10 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + prismjs@1.27.0: {} + + prismjs@1.29.0: {} + process-nextick-args@2.0.1: {} prompts@2.4.2: @@ -11345,6 +11511,10 @@ snapshots: object-assign: 4.1.1 react-is: 16.13.1 + property-information@5.6.0: + dependencies: + xtend: 4.0.2 + proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 @@ -11403,6 +11573,16 @@ snapshots: react-refresh@0.14.2: {} + react-syntax-highlighter@15.6.1(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.0 + highlight.js: 10.7.3 + highlightjs-vue: 1.0.0 + lowlight: 1.20.0 + prismjs: 1.29.0 + react: 18.3.1 + refractor: 3.6.0 + react-transition-group@4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.26.0 @@ -11412,6 +11592,12 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + react-type-animation@3.2.0(prop-types@15.8.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + prop-types: 15.8.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react@18.3.1: dependencies: loose-envify: 1.4.0 @@ -11469,6 +11655,12 @@ snapshots: globalthis: 1.0.4 which-builtin-type: 1.1.4 + refractor@3.6.0: + dependencies: + hastscript: 6.0.0 + parse-entities: 2.0.0 + prismjs: 1.27.0 + regenerator-runtime@0.14.1: {} regexp.prototype.flags@1.5.3: @@ -11794,6 +11986,8 @@ snapshots: dependencies: whatwg-url: 7.1.0 + space-separated-tokens@1.1.5: {} + spawndamnit@2.0.0: dependencies: cross-spawn: 5.1.0 From 9beb96d81675f820fb86d28f4e035b09113aa3a3 Mon Sep 17 00:00:00 2001 From: Geonhyuk Seo Date: Fri, 29 Nov 2024 06:30:34 +0900 Subject: [PATCH 052/124] =?UTF-8?q?=F0=9F=90=9E=20Fix(front):=20=EB=8F=84?= =?UTF-8?q?=EC=BB=A4=20=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EB=B9=8C?= =?UTF-8?q?=EB=93=9C=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95=20=EB=B0=8F?= =?UTF-8?q?=20=ED=97=88=EB=B8=8C=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EB=84=A4?= =?UTF-8?q?=EC=9D=B4=EB=B2=84=20=ED=81=B4=EB=9D=BC=EC=9A=B0=EB=93=9C=20?= =?UTF-8?q?=EB=A0=88=EC=A7=80=EC=8A=A4=ED=8A=B8=EB=A6=AC=EC=97=90=20?= =?UTF-8?q?=EC=98=AC=EB=9D=BC=EA=B0=80=EB=8F=84=EB=A1=9D=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci-cd.yml | 12 +++++++- apps/client/Dockerfile | 42 ++++++++++++++++++-------- docker-composes/cloud-canvas-local.yml | 20 ++++++------ 3 files changed, 50 insertions(+), 24 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index b2259ca7..c313ae74 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -26,12 +26,22 @@ jobs: uses: docker/build-push-action@v3 with: context: . - file: ./apps/client/Dockerfile + file: ./apps/front/Dockerfile push: true tags: | cloud-canvas.kr.ncr.ntruss.com/front:dev cloud-canvas.kr.ncr.ntruss.com/front:${{ github.sha }} + - name: Docker front-hub image build and push + uses: docker/build-push-action@v3 + with: + context: . + file: ./apps/hub/Dockerfile + push: true + tags: | + cloud-canvas.kr.ncr.ntruss.com/front-hub:dev + cloud-canvas.kr.ncr.ntruss.com/front-hub:${{ github.sha }} + - name: Docker back image build and push uses: docker/build-push-action@v3 with: diff --git a/apps/client/Dockerfile b/apps/client/Dockerfile index 3b645bc4..f975f72c 100644 --- a/apps/client/Dockerfile +++ b/apps/client/Dockerfile @@ -1,19 +1,35 @@ -FROM node:20 AS development -WORKDIR /development -COPY ./pnpm-lock.yaml ./apps/client/package.json . -RUN npm install -g pnpm && pnpm install +# FROM node:20 AS development +# WORKDIR /development +# COPY ./pnpm-lock.yaml ./apps/client/package.json . +# RUN npm install -g pnpm && pnpm install + +# FROM node:20 AS build +# WORKDIR /build +# RUN mkdir -p /build/apps/client +# RUN mkdir -p /build/config +# COPY --from=development /development/node_modules/ /build/apps/client/node_modules +# COPY --from=development /development/pnpm-lock.yaml /build/apps/client/ +# RUN npm install -g pnpm typescript +# COPY ./config/ /build/config/ +# COPY ./apps/client/ /build/apps/client/ +# WORKDIR /build/apps/client +# RUN npm run build && rm -rf node_modules && pnpm install --frozen-lockfile --prod + +# FROM nginx:alpine AS production +# COPY --from=build /build/apps/client/dist /usr/share/nginx/html +# COPY ./apps/nginx/nginx.conf /etc/nginx/conf.d/default.conf + +# ENV PORT=5000 +# EXPOSE 5000 +# CMD ["nginx", "-g", "daemon off;"] FROM node:20 AS build + WORKDIR /build -RUN mkdir -p /build/apps/client -RUN mkdir -p /build/config -COPY --from=development /development/node_modules/ /build/apps/client/node_modules -COPY --from=development /development/pnpm-lock.yaml /build/apps/client/ -RUN npm install -g pnpm typescript -COPY ./config/ /build/config/ -COPY ./apps/client/ /build/apps/client/ -WORKDIR /build/apps/client -RUN npm run build && rm -rf node_modules && pnpm install --frozen-lockfile --prod + +COPY . . + +RUN npm install -g pnpm && pnpm install && pnpm build FROM nginx:alpine AS production COPY --from=build /build/apps/client/dist /usr/share/nginx/html diff --git a/docker-composes/cloud-canvas-local.yml b/docker-composes/cloud-canvas-local.yml index 0f4e5b37..68e9db0f 100644 --- a/docker-composes/cloud-canvas-local.yml +++ b/docker-composes/cloud-canvas-local.yml @@ -79,16 +79,16 @@ services: - cloud-canvas-network restart: unless-stopped - # front: - # build: - # context: ../ - # dockerfile: apps/client/Dockerfile - # container_name: front - # ports: - # - '5001:5000' - # networks: - # - cloud-canvas-network - # restart: unless-stopped + front: + build: + context: ../ + dockerfile: apps/client/Dockerfile + container_name: front + ports: + - '5001:5000' + networks: + - cloud-canvas-network + restart: unless-stopped fluentd: build: ./monitorin/logging/fluentd/ From 250916c3f6fcadbea9215a6f79c22e0e8ff3a92f Mon Sep 17 00:00:00 2001 From: Geonhyuk Seo Date: Fri, 29 Nov 2024 06:34:27 +0900 Subject: [PATCH 053/124] =?UTF-8?q?=F0=9F=90=9E=20Fix(.github):=20?= =?UTF-8?q?=EA=B9=83=ED=97=88=EB=B8=8C=20=EC=95=A1=EC=85=98=20=EC=8A=A4?= =?UTF-8?q?=ED=81=AC=EB=A6=BD=ED=8A=B8=20=ED=94=84=EB=A1=A0=ED=8A=B8=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=ED=8C=8C=EC=9D=BC=20=EA=B2=BD?= =?UTF-8?q?=EB=A1=9C=20=EC=9E=98=EB=AA=BB=20=EC=B0=B8=EC=A1=B0=EB=90=9C=20?= =?UTF-8?q?=EA=B2=83=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci-cd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index c313ae74..0498e9d3 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -26,7 +26,7 @@ jobs: uses: docker/build-push-action@v3 with: context: . - file: ./apps/front/Dockerfile + file: ./apps/client/Dockerfile push: true tags: | cloud-canvas.kr.ncr.ntruss.com/front:dev From adb8359d40fefa18129ed673f48731e8a872cb6f Mon Sep 17 00:00:00 2001 From: Geonhyuk Seo Date: Fri, 29 Nov 2024 07:39:56 +0900 Subject: [PATCH 054/124] =?UTF-8?q?=F0=9F=90=9E=20Fix(docker-composes):=20?= =?UTF-8?q?=EB=8F=84=EC=BB=A4=20=EC=BB=B4=ED=8F=AC=EC=A6=88=20=EB=B0=B0?= =?UTF-8?q?=ED=8F=AC=20=EC=9D=BC=EB=B6=80=20=EC=98=A4=EB=A5=98=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/hub/Dockerfile | 2 +- docker-composes/cloud-canvas-back.yml | 2 ++ docker-composes/cloud-canvas-front-hub.yml | 13 +++++++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 docker-composes/cloud-canvas-front-hub.yml diff --git a/apps/hub/Dockerfile b/apps/hub/Dockerfile index c4617690..bea6d932 100644 --- a/apps/hub/Dockerfile +++ b/apps/hub/Dockerfile @@ -13,7 +13,7 @@ FROM node:20-alpine AS production WORKDIR /app RUN mkdir -p /app/server COPY --from=build /build/.next/standalone/ ./standalone/ -COPY --from=build /build/.next/static/ ./standalone/.next/ +COPY --from=build /build/.next/static/ ./standalone/.next/static ENV NEXT_PUBLIC_BACK_URL=http://localhost:3000 ENTRYPOINT ["sh", "-c", "node standalone/server.js"] \ No newline at end of file diff --git a/docker-composes/cloud-canvas-back.yml b/docker-composes/cloud-canvas-back.yml index 818cf247..13dce152 100644 --- a/docker-composes/cloud-canvas-back.yml +++ b/docker-composes/cloud-canvas-back.yml @@ -5,6 +5,8 @@ services: environment: NODE_ENV: production DATABASE_URL: ${DATABASE_URL} + NCLOUD_ACCESS_KEY: ${NCLOUD_ACCESS_KEY} + NCLOUD_SECRET_KEY: ${NCLOUD_SECRET_KEY} REDIS_HOST: ${REDIS_HOST} REDIS_PORT: ${REDIS_PORT} ports: diff --git a/docker-composes/cloud-canvas-front-hub.yml b/docker-composes/cloud-canvas-front-hub.yml new file mode 100644 index 00000000..f7bf6d7c --- /dev/null +++ b/docker-composes/cloud-canvas-front-hub.yml @@ -0,0 +1,13 @@ +services: + front-hub: + image: cloud-canvas.kr.ncr.ntruss.com/front-hub:dev + container_name: front-hub + ports: + - '3000:3000' + networks: + - cloud-canvas-network + restart: unless-stopped + pull_policy: always +networks: + cloud-canvas-network: + driver: bridge From 517a8c076263d26eeb7343e8db9331c87f3d2571 Mon Sep 17 00:00:00 2001 From: Geonhyuk Seo Date: Fri, 29 Nov 2024 07:56:25 +0900 Subject: [PATCH 055/124] =?UTF-8?q?=F0=9F=90=9E=20Fix(docker-composes):=20?= =?UTF-8?q?=EB=8F=84=EC=BB=A4=20=EC=BB=B4=ED=8F=AC=EC=A6=88=20=EB=B0=B1?= =?UTF-8?q?=EC=97=94=EB=93=9C=20=EC=84=9C=EB=B2=84=20=EC=8B=A4=ED=96=89=20?= =?UTF-8?q?=EC=95=88=EB=90=98=EB=8A=94=20=EB=B2=84=EA=B7=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-composes/cloud-canvas-back.yml | 2 +- docker-composes/cloud-canvas-front-hub.yml | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docker-composes/cloud-canvas-back.yml b/docker-composes/cloud-canvas-back.yml index 13dce152..95535f4c 100644 --- a/docker-composes/cloud-canvas-back.yml +++ b/docker-composes/cloud-canvas-back.yml @@ -11,7 +11,7 @@ services: REDIS_PORT: ${REDIS_PORT} ports: - '3000:3000' - entrypoint: sh -c "npx prisma generate && npx prisma migrate reset --force && node ./dist/src/main.js" + entrypoint: sh -c "cd apps/server && npx prisma migrate reset --force && node ./dist/src/main.js" networks: - cloud-canvas-network restart: unless-stopped diff --git a/docker-composes/cloud-canvas-front-hub.yml b/docker-composes/cloud-canvas-front-hub.yml index f7bf6d7c..818f5398 100644 --- a/docker-composes/cloud-canvas-front-hub.yml +++ b/docker-composes/cloud-canvas-front-hub.yml @@ -2,6 +2,8 @@ services: front-hub: image: cloud-canvas.kr.ncr.ntruss.com/front-hub:dev container_name: front-hub + environment: + NEXT_PUBLIC_BACK_URL: ${NEXT_PUBLIC_BACK_URL} ports: - '3000:3000' networks: From af1b823875c62dd763edacd7ed0bf33cbc690ffb Mon Sep 17 00:00:00 2001 From: Geonhyuk Seo Date: Fri, 29 Nov 2024 15:45:48 +0900 Subject: [PATCH 056/124] =?UTF-8?q?=F0=9F=90=9E=20Fix(hub):=20env=20back?= =?UTF-8?q?=20url=20=EB=AA=BB=20=EA=B0=80=EC=A0=B8=EC=98=A4=EB=8A=94=20?= =?UTF-8?q?=EB=B2=84=EA=B7=B8=20=EC=9E=84=EC=8B=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/hub/Dockerfile | 2 +- apps/hub/next.config.ts | 3 +++ apps/hub/src/app/architectures/[id]/page.tsx | 6 +++--- apps/hub/src/app/my/architectures/page.tsx | 2 +- apps/hub/src/app/my/shared/page.tsx | 2 +- apps/hub/src/app/my/starred/page.tsx | 2 +- apps/hub/src/app/page.tsx | 2 +- apps/hub/src/components/GlobalHeader/index.tsx | 13 +++++-------- docker-composes/cloud-canvas-front-hub.yml | 2 +- 9 files changed, 17 insertions(+), 17 deletions(-) diff --git a/apps/hub/Dockerfile b/apps/hub/Dockerfile index bea6d932..4ce43f43 100644 --- a/apps/hub/Dockerfile +++ b/apps/hub/Dockerfile @@ -15,5 +15,5 @@ RUN mkdir -p /app/server COPY --from=build /build/.next/standalone/ ./standalone/ COPY --from=build /build/.next/static/ ./standalone/.next/static -ENV NEXT_PUBLIC_BACK_URL=http://localhost:3000 +ENV BACK_URL=https://api.cloudcanvas.kro.kr ENTRYPOINT ["sh", "-c", "node standalone/server.js"] \ No newline at end of file diff --git a/apps/hub/next.config.ts b/apps/hub/next.config.ts index fa3438ad..c46e7b66 100644 --- a/apps/hub/next.config.ts +++ b/apps/hub/next.config.ts @@ -3,6 +3,9 @@ import type { NextConfig } from 'next'; const nextConfig: NextConfig = { /* config options here */ output: 'standalone', + env: { + BACK_URL: 'https://api.cloudcanvas.kro.kr', + }, }; export default nextConfig; diff --git a/apps/hub/src/app/architectures/[id]/page.tsx b/apps/hub/src/app/architectures/[id]/page.tsx index 2688335a..e74acb86 100644 --- a/apps/hub/src/app/architectures/[id]/page.tsx +++ b/apps/hub/src/app/architectures/[id]/page.tsx @@ -26,7 +26,7 @@ interface PublicArchitecture { export default function ArchitectureDetailPage() { const params = useParams<{ id: string }>(); const { data, error, isLoading, mutate } = useSWR( - `${process.env.NEXT_PUBLIC_BACK_URL}/public-architectures/${params.id}`, + `${process.env.BACK_URL}/public-architectures/${params.id}`, fetcher, ); @@ -48,7 +48,7 @@ export default function ArchitectureDetailPage() { const toggleStar = async () => { await fetch( - `${process.env.NEXT_PUBLIC_BACK_URL}/public-architectures/${params.id}/stars`, + `${process.env.BACK_URL}/public-architectures/${params.id}/stars`, { method: data!.stars.length > 0 ? 'DELETE' : 'POST', credentials: 'include', @@ -59,7 +59,7 @@ export default function ArchitectureDetailPage() { const handleImport = async () => { await fetch( - `${process.env.NEXT_PUBLIC_BACK_URL}/public-architectures/${params.id}/imports`, + `${process.env.BACK_URL}/public-architectures/${params.id}/imports`, { method: 'POST', credentials: 'include', diff --git a/apps/hub/src/app/my/architectures/page.tsx b/apps/hub/src/app/my/architectures/page.tsx index 6e4cab34..f2530565 100644 --- a/apps/hub/src/app/my/architectures/page.tsx +++ b/apps/hub/src/app/my/architectures/page.tsx @@ -7,7 +7,7 @@ export default function MyArchitecturesPage() { return ( ); diff --git a/apps/hub/src/app/my/shared/page.tsx b/apps/hub/src/app/my/shared/page.tsx index 59b45f4e..a9f63aee 100644 --- a/apps/hub/src/app/my/shared/page.tsx +++ b/apps/hub/src/app/my/shared/page.tsx @@ -6,7 +6,7 @@ export default function MySharedPage() { return ( ); diff --git a/apps/hub/src/app/my/starred/page.tsx b/apps/hub/src/app/my/starred/page.tsx index 0bcc9961..9f98c2a1 100644 --- a/apps/hub/src/app/my/starred/page.tsx +++ b/apps/hub/src/app/my/starred/page.tsx @@ -6,7 +6,7 @@ export default function MyStarredPage() { return ( ); diff --git a/apps/hub/src/app/page.tsx b/apps/hub/src/app/page.tsx index 2284fea7..60ecebd7 100644 --- a/apps/hub/src/app/page.tsx +++ b/apps/hub/src/app/page.tsx @@ -6,7 +6,7 @@ export default function Home() { return ( ); diff --git a/apps/hub/src/components/GlobalHeader/index.tsx b/apps/hub/src/components/GlobalHeader/index.tsx index b53223cf..0a9757db 100644 --- a/apps/hub/src/components/GlobalHeader/index.tsx +++ b/apps/hub/src/components/GlobalHeader/index.tsx @@ -16,13 +16,10 @@ export const GlobalHeader = () => { }, []); const handleLogin = async () => { - const res = await fetch( - `${process.env.NEXT_PUBLIC_BACK_URL}/auth/login`, - { - method: 'POST', - credentials: 'include', - }, - ); + const res = await fetch(`${process.env.BACK_URL}/auth/login`, { + method: 'POST', + credentials: 'include', + }); if (res.ok) { setIsLoggedIn(true); localStorage.setItem('isLoggedIn', 'true'); @@ -32,7 +29,7 @@ export const GlobalHeader = () => { }; const handleLogout = () => { - fetch(`${process.env.NEXT_PUBLIC_BACK_URL}/auth/logout`, { + fetch(`${process.env.BACK_URL}/auth/logout`, { method: 'POST', credentials: 'include', }); diff --git a/docker-composes/cloud-canvas-front-hub.yml b/docker-composes/cloud-canvas-front-hub.yml index 818f5398..df9d9107 100644 --- a/docker-composes/cloud-canvas-front-hub.yml +++ b/docker-composes/cloud-canvas-front-hub.yml @@ -3,7 +3,7 @@ services: image: cloud-canvas.kr.ncr.ntruss.com/front-hub:dev container_name: front-hub environment: - NEXT_PUBLIC_BACK_URL: ${NEXT_PUBLIC_BACK_URL} + BACK_URL: ${BACK_URL} ports: - '3000:3000' networks: From c288f826a9ec46d1419d6ed67d64dd673583d9c7 Mon Sep 17 00:00:00 2001 From: Geonhyuk Seo Date: Fri, 29 Nov 2024 15:57:40 +0900 Subject: [PATCH 057/124] =?UTF-8?q?=F0=9F=90=9E=20Fix(server):=20cors=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/server/src/main.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/apps/server/src/main.ts b/apps/server/src/main.ts index f661715c..95be9798 100644 --- a/apps/server/src/main.ts +++ b/apps/server/src/main.ts @@ -48,6 +48,22 @@ async function bootstrap() { credentials: true, }); } + else { + app.enableCors({ + origin: 'https://cloudcanvas.kro.kr', + methods: [ + 'GET', + 'HEAD', + 'PUT', + 'PATCH', + 'POST', + 'DELETE', + 'OPTIONS', + ], + allowedHeaders: ['Content-Type', 'Authorization', 'Accept'], + credentials: true, + }); + } await app.listen(3000); } bootstrap(); From 22027fa0d33deee0d7171d7a315e6eff9b45e002 Mon Sep 17 00:00:00 2001 From: Gdm0714 Date: Sun, 1 Dec 2024 00:44:42 +0900 Subject: [PATCH 058/124] =?UTF-8?q?=F0=9F=93=9D=20Docs:=20readme=20?= =?UTF-8?q?=EC=A0=84=EB=B0=98=EC=A0=81=EC=9D=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 108 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 77 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index bfc1b661..88c07263 100644 --- a/README.md +++ b/README.md @@ -1,60 +1,106 @@ -# Cloud Canvas - -

    +

    Cloud Canvas -

    +

    🎨 Cloud Canvas 🎨

    +

    쉽고 빠르게, 누구나 클라우드를 설계하는 즐거운 경험을!

    + +## **Cloud Canvas란?** ✨ + +Cloud Canvas는 클라우드 인프라 설계를 **그래픽 인터페이스**로 간단하고 직관적으로 할 수 있는 **혁신적인 도구**입니다. +국내 클라우드 플랫폼을 적극 지원하며, **Terraform 코드 변환**까지 가능한 **올인원 인프라 관리 플랫폼**입니다. + +> **국내 클라우드에도 이런 도구가 필요하지 않으셨나요?** +> Cloud Canvas와 함께 **더 쉽고, 더 빠르고, 더 즐겁게** 클라우드 인프라를 설계하세요! + +--- + +## **프로젝트 비전** 🚀 + +### 왜 Cloud Canvas인가? + +**개발자들의 공통된 고민** + +- 반복되는 수작업으로 클라우드 리소스를 관리해야 하는 불편함. +- 클라우드 플랫폼의 복잡한 인터페이스에 적응하는 데 필요한 시간과 노력. +- 국내 클라우드 서비스에 적합한 툴 부재. + +**우리의 해답** -## 📌 프로젝트 배경 +- **Cloud Canvas는 국내 클라우드 플랫폼을 목표로 하는 GUI 기반 도구**입니다. + 클릭 몇 번으로 완성되는 설계부터 Terraform 코드 변환까지, Cloud Canvas가 여러분의 시간을 아껴드립니다. -클라우드 인프라 구축 과정에서 개발자들은 다음과 같은 어려움을 겪고 있습니다 +--- -- **반복적인 수작업**: 클라우드 콘솔에서 각 리소스마다 별도의 페이지에 접속하여 생성하는 과정을 반복해야 합니다. -- **플랫폼 적응 시간**: 클라우드 업체별로 상이한 인터페이스로 인해, 새로운 플랫폼 사용 시 추가적인 학습 시간이 필요합니다. +## **프로젝트 목표** 🎯 -이러한 인프라 관리 작업들로 인해 핵심 개발 업무에 집중하기 어려워집니다. +### 🖌️ **직관적인 UX/UI** -최근에는 위의 문제들을 해결하기 위한 도구들이 제공되고 있지만, 다음과 같은 한계점을 가지고 있습니다. +- 누구나 쉽게 클라우드 인프라를 설계할 수 있는 **직관적이고 세련된 인터페이스** 제공. -- **Terraform 등 IaC 도구**: 높은 러닝 커브와 Terraform을 활용하여 프로젝트를 관리하는 방법을 학습하는 데 개발자들이 시간을 투자하여야 합니다. -- **AWS CloudFormation Design, CloudCraft 등 GUI 인프라 설계 도구**: AWS CloudFormation Design은 AWS 전용으로 제공하는 기능이며, CloudCraft는 외국의 클라우드 업체만을 대상으로 하기에 국내 클라우드 업체를 사용하는 개발자들이 해당 도구들을 사용할 수 없습니다. +### 🔧 **자동화된 Terraform 코드 변환** -따라서 저희 Cloud-Canvas 팀은 국내 클라우드 업체도 지원하는 GUI 기반의 인프라 통합 관리 시스템에 필요성을 느끼게 되었습니다. +- 설계된 인프라를 **Terraform 코드로 변환**하여 다운로드 가능. -## 📌 프로젝트 소개 +### 🌐 **인프라 허브 기능** -Cloud-Canvas는 이러한 문제점을 해결하기 위한 GUI 기반 인프라 관리 도구입니다. +- 설계한 인프라를 **공유하고 재활용**할 수 있는 커뮤니티 제공. +- 다른 사용자의 인프라를 **수정, 활용**하여 자신만의 설계를 완성. -## 📌 프로젝트 기대 효과 +--- -- 기존 AWS 사용자들의 국내 클라우드 생태계 유입 촉진 -- 국내 클라우드 서비스 활성화 -- 한국 클라우드 산업의 경쟁력 강화 +## **Cloud Canvas의 기대 효과** 🌟 -## 📌 프로젝트 목표 +1. **반복 작업 최소화** + - GUI로 빠르고 효율적인 설계 가능. +2. **국내 클라우드 생태계 활성화** + - AWS 사용자들이 국내 클라우드 플랫폼으로 쉽게 유입. +3. **한국 클라우드 산업 경쟁력 강화** + - 글로벌 시장에서도 경쟁 가능한 혁신적 도구. -- 직관적인 UX/UI를 통한 인프라 설계 기능 제공 -- 실시간 모니터링을 통한 효율적인 인프라 관리 기능 제공 -- 국내외 클라우드 업체 통합 관리 기능 제공 -- NPM 모듈을 통해 제공함으로써, 자바스크립트 개발자들에게 친화적인 도구를 제작 -- Infra Hub를 통해 사용자가 자신이 설계한 인프라를 전 세계 사람들과 공유할 수 있도록 함 +--- -## 📌 아키텍쳐 +## **기술 스택** 🛠 -### 전반적인 인프라 +### 📌 **Frontend** + +- **React 18.3.1** +- **Vite 5.4.9** + +### 📌 **Backend** + +- **TypeScript 5.1.3** +- **NestJS 10.0.0** +- **Prisma 5.22.0** +- **Vitest 2.1.4** +- **MySQL** + +--- + +## **아키텍처** 🌐 + +### **전반적인 인프라 설계** ![image](https://github.com/user-attachments/assets/5901b688-0d3d-4698-ad22-a4d4bb7aa8fd) -### CI/CD +### **CI/CD 파이프라인** cicd -# 팀 +## **팀 소개** 👩‍💻 + +> 다양한 배경과 경험을 가진 네 명의 팀원이 Cloud Canvas를 만들고 있습니다. -| 김범준 | 고동민 | 최재영 | 서건혁 | +| **김범준** | **고동민** | **최재영** | **서건혁** | | :--------------------------------------------------------: | :-------------------------------------------------------: | :-------------------------------------------------------: | :-------------------------------------------------------: | -| FE | BE | BE | BE | +| **FE** | **BE** | **BE** | **BE** | | [p1n9](https://github.com/p1n9d3v) | [Gdm0714](https://github.com/Gdm0714) | [paulcjy](https://github.com/paulcjy) | [SeoGeonhyuk](https://github.com/SeoGeonhyuk) | | ![](https://avatars.githubusercontent.com/u/152015839?v=4) | ![](https://avatars.githubusercontent.com/u/50660440?v=4) | ![](https://avatars.githubusercontent.com/u/86853786?v=4) | ![](https://avatars.githubusercontent.com/u/60954160?v=4) | | 커피 | 빵 | 고기 | 국수 | + +--- + +## 🌈 **함께하세요!** + +> **Cloud Canvas로 클라우드 설계의 새로운 가능성을 경험해보세요!** +> 프로젝트의 진행 상황과 더 많은 정보를 원하신다면 [GitHub Wiki](https://github.com/boostcampwm-2024/web37-cloud-canvas/wiki)에서 확인하세요. 😊 From 55a95926e7de68fad7a6648d36803ab6a8926e05 Mon Sep 17 00:00:00 2001 From: p1n9d3v Date: Sun, 1 Dec 2024 03:56:34 +0900 Subject: [PATCH 059/124] =?UTF-8?q?=F0=9F=90=9E=20Fix(client):=20connector?= =?UTF-8?q?=20=EB=B0=8F=203d=20=EA=B7=B8=EB=A3=B9=ED=99=94=20=ED=81=AC?= =?UTF-8?q?=EA=B8=B0=20=EA=B3=84=EC=82=B0=20=EB=B2=84=EA=B7=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/client/src/CloudGraph.tsx | 2 + .../components/Group/ncloud/RegionGroup.tsx | 6 +- apps/client/src/components/Node/index.tsx | 16 ++- .../Node/ncloud/ContainerRegistry.tsx | 94 ++++++++++--- .../{DBMySQLNode.tsx => MySQLDBNode.tsx} | 0 .../src/components/Node/ncloud/NatGateway.tsx | 108 +++++++++++++++ apps/client/src/constants/index.ts | 31 ++--- apps/client/src/helpers/group.ts | 88 +++++++----- apps/client/src/helpers/node.ts | 34 ++--- apps/client/src/hooks/useGraph.ts | 28 ++-- .../client/src/models/ncloud/CloudFunction.ts | 6 +- .../src/models/ncloud/ContainerRegistry.ts | 6 +- .../{LoadBalancerNode.ts => LoadBalancer.ts} | 6 +- apps/client/src/models/ncloud/MySQLDB.ts | 6 +- apps/client/src/models/ncloud/NatGateway.ts | 25 ++++ .../{ObjectStorage.tsx => ObjectStorage.ts} | 6 +- apps/client/src/models/ncloud/Server.ts | 6 +- apps/client/src/models/ncloud/index.ts | 9 +- apps/client/src/types/index.ts | 12 +- apps/client/src/utils/index.ts | 126 +++++++++++++++--- 20 files changed, 475 insertions(+), 140 deletions(-) rename apps/client/src/components/Node/ncloud/{DBMySQLNode.tsx => MySQLDBNode.tsx} (100%) create mode 100644 apps/client/src/components/Node/ncloud/NatGateway.tsx rename apps/client/src/models/ncloud/{LoadBalancerNode.ts => LoadBalancer.ts} (77%) create mode 100644 apps/client/src/models/ncloud/NatGateway.ts rename apps/client/src/models/ncloud/{ObjectStorage.tsx => ObjectStorage.ts} (67%) diff --git a/apps/client/src/CloudGraph.tsx b/apps/client/src/CloudGraph.tsx index 2bf94889..1c48c769 100644 --- a/apps/client/src/CloudGraph.tsx +++ b/apps/client/src/CloudGraph.tsx @@ -80,6 +80,8 @@ export default () => { updatedPointForDimension(); }, [dimension]); + + console.log(nodes); return ( diff --git a/apps/client/src/components/Group/ncloud/RegionGroup.tsx b/apps/client/src/components/Group/ncloud/RegionGroup.tsx index 2d222447..0308d255 100644 --- a/apps/client/src/components/Group/ncloud/RegionGroup.tsx +++ b/apps/client/src/components/Group/ncloud/RegionGroup.tsx @@ -36,9 +36,9 @@ const Region3D = ({ bounds, properties, color }: Props) => { }); const points = ` - ${point1.x} ${point1.y}, - ${point2.x} ${point2.y}, - ${point3.x} ${point3.y}, + ${point1.x} ${point1.y}, + ${point2.x} ${point2.y}, + ${point3.x} ${point3.y}, ${point4.x} ${point4.y} `; diff --git a/apps/client/src/components/Node/index.tsx b/apps/client/src/components/Node/index.tsx index 651ca763..7db83c70 100644 --- a/apps/client/src/components/Node/index.tsx +++ b/apps/client/src/components/Node/index.tsx @@ -1,11 +1,13 @@ import CloudFunctionNode from '@components/Node/ncloud/CloudFunctionNode'; -import DBMySQLNode from '@components/Node/ncloud/DBMySQLNode'; +import ContainerRegistryNode from '@components/Node/ncloud/ContainerRegistry'; +import MySQLDBNode from '@components/Node/ncloud/MySQLDBNode'; import ObjectStorageNode from '@components/Node/ncloud/ObjectStorageNode'; import ServerNode from '@components/Node/ncloud/ServerNode'; import useDrag from '@hooks/useDrag'; import { Node, Point } from '@types'; import { useEffect } from 'react'; -import LoadBalancer from './ncloud/LoadBalancer'; +import LoadBalancerNode from './ncloud/LoadBalancer'; +import NatGatewayNode from './ncloud/NatGateway'; const nodeFactory = (node: Node) => { switch (node.type) { @@ -16,13 +18,15 @@ const nodeFactory = (node: Node) => { case 'object-storage': return ; case 'db-mysql': - return ; + return ; case 'load-balancer': - return ; + return ; case 'object-storage': return ; - // case 'container-registry': - // return ; + case 'container-registry': + return ; + case 'nat-gateway': + return ; default: null; } diff --git a/apps/client/src/components/Node/ncloud/ContainerRegistry.tsx b/apps/client/src/components/Node/ncloud/ContainerRegistry.tsx index 206cda70..c80e251b 100644 --- a/apps/client/src/components/Node/ncloud/ContainerRegistry.tsx +++ b/apps/client/src/components/Node/ncloud/ContainerRegistry.tsx @@ -4,21 +4,79 @@ import { Node } from '@types'; type Props = Partial; //TODO: const Node3D = ({ properties }: Props) => { - const width = 512; - const height = 296; - const point = `${width / 2},0 ${width},${height / 2} ${width / 2},${height} 0,${height / 2}`; - const isoMatrix = new DOMMatrix() - .rotate(30) - .skewX(-30) - .scale(1, 0.8602) - .translate(0, 0); return ( - + + + + + + + + + + + + + + + + + + + + ECS Cluster + + + ECS Cluster + + + + ); }; @@ -52,11 +110,11 @@ const Node2D = ({ properties }: Props) => { ; + +const convertToIsoMatrix = (x: number, y: number) => { + const isoMatrix = new DOMMatrix() + .rotate(30) + .skewX(-30) + .scale(1, 0.8602) + .translate(x, y); + + return isoMatrix; // 결과 행렬 반환 +}; +const Node3D = ({ properties }: Props) => { + const matrix = convertToIsoMatrix(0, 0); + + return ( + + + + + + + + + + + + ); +}; + +const Node2D = ({ properties }: Props) => { + return ( + + + + + + + + + + ); +}; + +export default ({ properties }: Partial) => { + const { dimension } = useDimensionContext(); + return dimension === '2d' ? ( + + ) : ( + + ); +}; diff --git a/apps/client/src/constants/index.ts b/apps/client/src/constants/index.ts index 9f24032b..39a4acf9 100644 --- a/apps/client/src/constants/index.ts +++ b/apps/client/src/constants/index.ts @@ -22,21 +22,21 @@ export const NCLOUD_SERVICES = [ }, ], }, - // { - // title: 'container', - // items: [ - // { - // title: 'Container Registry', - // desc: 'Container Registry', - // type: 'container-registry', - // }, - // { - // title: 'Kubernetes', - // desc: 'NCloud Kubernetes Service', - // type: 'Kubernetes', - // }, - // ], - // }, + { + title: 'container', + items: [ + { + title: 'Container Registry', + desc: 'Container Registry', + type: 'container-registry', + }, + // { + // title: 'Kubernetes', + // desc: 'NCloud Kubernetes Service', + // type: 'Kubernetes', + // }, + ], + }, { title: 'storage', items: [ @@ -85,6 +85,7 @@ export const NCLOUD_SERVICES = [ desc: 'load balancing', type: 'load-balancer', }, + { title: 'Nat Gateway', desc: 'nat gateway', type: 'nat-gateway' }, ], }, ]; diff --git a/apps/client/src/helpers/group.ts b/apps/client/src/helpers/group.ts index 29cd1e35..371dbaad 100644 --- a/apps/client/src/helpers/group.ts +++ b/apps/client/src/helpers/group.ts @@ -1,6 +1,7 @@ -import { GRID_2D_SIZE } from '@constants'; -import { Bounds, Dimension, Group } from '@types'; +import { NODE_BASE_SIZE } from '@constants'; +import { Dimension, Group, Node, Size3D } from '@types'; import { convert2dTo3dPoint, convert3dTo2dPoint } from '@utils'; +import { getNodeOffsetForDimension } from './node'; export const GraphGroup = { id: '', @@ -11,44 +12,67 @@ export const GraphGroup = { parentGroupId: '', }; -export const computeBounds = (_bounds: Bounds[], dimension: Dimension) => { - const padding = GRID_2D_SIZE * 2; - let bounds = _bounds; - if (dimension === '3d') { - bounds = bounds.map((bound) => ({ - ...bound, - ...convert3dTo2dPoint({ - x: bound.x, - y: bound.y, - }), - })); - } +export const computeBounds = ( + nodes: Node[], + dimension: Dimension, + paddingSize: number = 1, +) => { + const padding = 90 * paddingSize; - const minX = Math.min(...bounds.map((bounds) => bounds.x)); - const minY = Math.min(...bounds.map((bounds) => bounds.y)); - const maxX = Math.max(...bounds.map((bounds) => bounds.x + bounds.width)); - const maxY = Math.max(...bounds.map((bounds) => bounds.y + bounds.height)); + if (dimension === '2d') { + const minX = Math.min(...nodes.map((node) => node.point.x)); + const minY = Math.min(...nodes.map((node) => node.point.y)); + const maxX = Math.max( + ...nodes.map((node) => node.point.x + node.size['2d'].width), + ); + const maxY = Math.max( + ...nodes.map((node) => node.point.y + node.size['2d'].height), + ); - let x = minX - padding; - let y = minY - padding; - let width = maxX - minX + padding * 2; - let height = maxY - minY + padding * 2; + return { + x: minX - padding, + y: minY - padding, + width: maxX - minX + padding * 2, + height: maxY - minY + padding * 2, + }; + } if (dimension === '3d') { - const minPoint = convert2dTo3dPoint({ + //2d + nodes = nodes.map((node) => { + const offset = getNodeOffsetForDimension( + node.size['3d'], + NODE_BASE_SIZE['3d'] as Size3D, + ); + const pos = convert3dTo2dPoint({ + x: node.point.x - offset.x, + y: node.point.y - offset.y, + }); + return { + ...node, + point: { x: pos.x, y: pos.y }, + }; + }); + const minX = Math.min(...nodes.map((node) => node.point.x)); + const minY = Math.min(...nodes.map((node) => node.point.y)); + const maxX = Math.max( + ...nodes.map((node) => node.point.x + node.size['2d'].width), + ); + const maxY = Math.max( + ...nodes.map((node) => node.point.y + node.size['2d'].height), + ); + + const { x, y } = convert2dTo3dPoint({ x: minX - padding, y: minY - padding, }); - x = minPoint.x; - y = minPoint.y; + return { + x, + y, + width: maxX - minX + padding * 2, + height: maxY - minY + padding * 2, + }; } - - return { - x, - y, - width, - height, - }; }; export const findParentGroup = ( diff --git a/apps/client/src/helpers/node.ts b/apps/client/src/helpers/node.ts index cdf97864..f39c5411 100644 --- a/apps/client/src/helpers/node.ts +++ b/apps/client/src/helpers/node.ts @@ -1,5 +1,5 @@ import { NODE_BASE_SIZE } from '@constants'; -import { Dimension, Node, Point, Size } from '@types'; +import { Dimension, Node, Point, Size, Size3D } from '@types'; import { alignPoint2d, alignPoint3d, @@ -14,8 +14,16 @@ export const GraphNode = { connectors: {}, }; -//TODO: 사이즈가 큰 노드를 다룰 떄 이상해짐 -const getNodeOffsetForDimension = (nodeSize: Size, baseSize: Size) => { +export const getNodeOffsetForDimension = ( + nodeSize: Size & Size3D, + baseSize: Size, +) => { + if (nodeSize.width % 128 === 0 && nodeSize.height % 111 === 0) { + return { + x: 0, + y: 0, + }; + } return { x: (baseSize.width - nodeSize.width) / 2, y: baseSize.height - nodeSize.height - (nodeSize.offset || 0), @@ -25,13 +33,14 @@ const getNodeOffsetForDimension = (nodeSize: Size, baseSize: Size) => { //INFO: 처음이 2d로 시작하기 때문에 nodeSize : 3d , baseSize : 3d로 해야함. 다른 방법은 잘 모르곘음. //2d에서 3d로 변환할 때는 3d에서 2d로 변환할 때와 달리 baseSize와 nodeSize가 2d 사이즈 들어가야 할 것 같음 export const adjustNodePointForDimension = ( - node: Node, + point: Point, + size: Size3D, dimension: Dimension, ) => { - const { point, size } = node; - - const offset = getNodeOffsetForDimension(size['3d'], NODE_BASE_SIZE['3d']); - console.log('offset', node.type, offset); + const offset = getNodeOffsetForDimension( + size, + NODE_BASE_SIZE['3d'] as Size3D, + ); let result; if (dimension === '2d') { result = convert3dTo2dPoint({ @@ -74,12 +83,3 @@ export const alignNodePoint = ( return result; }; - -export const getNodeBounds = (node: Node, dimension: Dimension) => { - return { - x: node.point.x, - y: node.point.y, - width: node.size[dimension].width, - height: node.size[dimension].height, - }; -}; diff --git a/apps/client/src/hooks/useGraph.ts b/apps/client/src/hooks/useGraph.ts index a0a93582..54f8b139 100644 --- a/apps/client/src/hooks/useGraph.ts +++ b/apps/client/src/hooks/useGraph.ts @@ -9,11 +9,7 @@ import { updateNearestConnectorPair, } from '@helpers/edge'; import { computeBounds } from '@helpers/group'; -import { - adjustNodePointForDimension, - alignNodePoint, - getNodeBounds, -} from '@helpers/node'; +import { adjustNodePointForDimension, alignNodePoint } from '@helpers/node'; import useSelection from '@hooks/useSelection'; import { Connection, Edge, Group, Node, Point } from '@types'; import { @@ -120,7 +116,12 @@ export default () => { const updateNodePointForDimension = () => { const updatedNodes = Object.entries(nodes).reduce((acc, [id, node]) => { - const adjustedPoint = adjustNodePointForDimension(node, dimension); + const adjustedPoint = adjustNodePointForDimension( + node.point, + node.size['3d'], + dimension, + ); + const connectors = getConnectorPoints( { ...node, point: adjustedPoint }, dimension, @@ -374,23 +375,20 @@ export default () => { const recursiveGroupBounds = (group: Group): any => { if (group.childGroupIds.length === 0) { - const nodesBounds = group.nodeIds.map((nodeId) => - getNodeBounds(nodes[nodeId], dimension), - ); + const innerNodes = group.nodeIds.map((nodeId) => nodes[nodeId]); - return computeBounds(nodesBounds, dimension); + return computeBounds(innerNodes, dimension, 2); } - const childGroupsBounds = group.childGroupIds.map((childGroupId) => + const childNodes = group.childGroupIds.map((childGroupId) => recursiveGroupBounds(groups[childGroupId]), ); - const currentNodesBounds = group.nodeIds.map((nodeId) => - getNodeBounds(nodes[nodeId], dimension), - ); + const currentNodes = group.nodeIds.map((nodeId) => nodes[nodeId]); return computeBounds( - [...currentNodesBounds, ...childGroupsBounds], + [...currentNodes, ...childNodes], dimension, + 2, ); }; diff --git a/apps/client/src/models/ncloud/CloudFunction.ts b/apps/client/src/models/ncloud/CloudFunction.ts index 365537f2..93acb5b6 100644 --- a/apps/client/src/models/ncloud/CloudFunction.ts +++ b/apps/client/src/models/ncloud/CloudFunction.ts @@ -13,9 +13,13 @@ export const CloudFunctionNode: Node & { type: 'cloud-function', size: { '2d': { width: 90, height: 90 }, - '3d': { width: 96, height: 113.438, offset: 10 }, + '3d': { width: 96, height: 113.438, depth: 58, offset: 12.5 }, }, properties: { ...Networks, }, + filterConnectorTypes: { + '2d': ['top', 'right', 'bottom', 'left'], + '3d': ['top', 'right', 'bottom', 'left'], + }, }; diff --git a/apps/client/src/models/ncloud/ContainerRegistry.ts b/apps/client/src/models/ncloud/ContainerRegistry.ts index 8a851d76..92740103 100644 --- a/apps/client/src/models/ncloud/ContainerRegistry.ts +++ b/apps/client/src/models/ncloud/ContainerRegistry.ts @@ -13,11 +13,15 @@ export const ContainerRegistryNode: Node & { type: 'container-registry', size: { '2d': { width: 360, height: 360 }, - '3d': { width: 360, height: 360 }, + '3d': { width: 512, height: 333, depth: 37, offset: 0 }, }, properties: { ...Networks, }, + filterConnectorTypes: { + '2d': ['top', 'right', 'bottom', 'left'], + '3d': ['top', 'right', 'bottom', 'left'], + }, }; export const ContainerRegistryRequiredFields = {}; diff --git a/apps/client/src/models/ncloud/LoadBalancerNode.ts b/apps/client/src/models/ncloud/LoadBalancer.ts similarity index 77% rename from apps/client/src/models/ncloud/LoadBalancerNode.ts rename to apps/client/src/models/ncloud/LoadBalancer.ts index f4f994ee..5f807b6d 100644 --- a/apps/client/src/models/ncloud/LoadBalancerNode.ts +++ b/apps/client/src/models/ncloud/LoadBalancer.ts @@ -15,7 +15,7 @@ export const LoadBalancerNode: Node & { type: 'load-balancer', size: { '2d': { width: 90, height: 90 }, - '3d': { width: 97, height: 94, offset: 10 }, + '3d': { width: 97, height: 94, depth: 38, offset: 10 }, }, properties: { ...Networks, @@ -23,6 +23,10 @@ export const LoadBalancerNode: Node & { networkType: undefined, subnetNoList: undefined, }, + filterConnectorTypes: { + '2d': ['top', 'right', 'bottom', 'left'], + '3d': ['top', 'right', 'bottom', 'left'], + }, }; export const LoadBalancerRequiredFields = { diff --git a/apps/client/src/models/ncloud/MySQLDB.ts b/apps/client/src/models/ncloud/MySQLDB.ts index 751f2c3d..51c7a11e 100644 --- a/apps/client/src/models/ncloud/MySQLDB.ts +++ b/apps/client/src/models/ncloud/MySQLDB.ts @@ -11,11 +11,15 @@ export const MySQLDBNode: Node = { type: 'db-mysql', size: { '2d': { width: 90, height: 90 }, - '3d': { width: 128, height: 137.5 }, + '3d': { width: 128, height: 137.5, depth: 82, offset: 0 }, }, properties: { ...Networks, }, + filterConnectorTypes: { + '2d': ['top', 'right', 'bottom', 'left'], + '3d': ['top', 'right', 'bottom', 'left'], + }, }; export const MySQLDBRequiredFields = {}; diff --git a/apps/client/src/models/ncloud/NatGateway.ts b/apps/client/src/models/ncloud/NatGateway.ts new file mode 100644 index 00000000..dcef2f28 --- /dev/null +++ b/apps/client/src/models/ncloud/NatGateway.ts @@ -0,0 +1,25 @@ +import { GraphNode } from '@helpers/node'; +import { Node } from '@types'; +import { Networks, NetworksProp } from './Networks'; + +export interface NatGatewayProp extends NetworksProp { + //TODO: +} + +export const NatGatewayNode: Node = { + ...GraphNode, + type: 'nat-gateway', + size: { + '2d': { width: 90, height: 90 }, + '3d': { width: 123, height: 108.05, depth: 74, offset: 0 }, + }, + properties: { + ...Networks, + }, + filterConnectorTypes: { + '2d': ['top', 'right', 'bottom', 'left'], + '3d': ['center'], + }, +}; + +export const NatGatewayRequiredFields = {}; diff --git a/apps/client/src/models/ncloud/ObjectStorage.tsx b/apps/client/src/models/ncloud/ObjectStorage.ts similarity index 67% rename from apps/client/src/models/ncloud/ObjectStorage.tsx rename to apps/client/src/models/ncloud/ObjectStorage.ts index 9a034495..52a06b76 100644 --- a/apps/client/src/models/ncloud/ObjectStorage.tsx +++ b/apps/client/src/models/ncloud/ObjectStorage.ts @@ -13,9 +13,13 @@ export const ObjectStorageNode: Node & { type: 'object-storage', size: { '2d': { width: 90, height: 90 }, - '3d': { width: 100.626, height: 115.695, offset: 20 }, + '3d': { width: 100.626, height: 115.695, depth: 58, offset: 20 }, }, properties: { ...Networks, }, + filterConnectorTypes: { + '2d': ['top', 'right', 'bottom', 'left'], + '3d': ['top', 'right', 'bottom', 'left'], + }, }; diff --git a/apps/client/src/models/ncloud/Server.ts b/apps/client/src/models/ncloud/Server.ts index 453d6c5f..be56002d 100644 --- a/apps/client/src/models/ncloud/Server.ts +++ b/apps/client/src/models/ncloud/Server.ts @@ -15,7 +15,7 @@ export const ServerNode: Node & { type: 'server', size: { '2d': { width: 90, height: 90 }, - '3d': { width: 128, height: 111 }, + '3d': { width: 128, height: 111, depth: 37, offset: 0 }, }, properties: { ...Networks, @@ -23,6 +23,10 @@ export const ServerNode: Node & { server_image_number: undefined, server_spec_code: undefined, }, + filterConnectorTypes: { + '2d': ['top', 'right', 'bottom', 'left'], + '3d': ['top', 'right', 'bottom', 'left'], + }, }; export const ServerRequiredFields = { diff --git a/apps/client/src/models/ncloud/index.ts b/apps/client/src/models/ncloud/index.ts index 338fb4b0..d8674af7 100644 --- a/apps/client/src/models/ncloud/index.ts +++ b/apps/client/src/models/ncloud/index.ts @@ -1,10 +1,11 @@ import { CloudFunctionNode } from './CloudFunction'; -import { LoadBalancerNode } from './LoadBalancerNode'; +import { ContainerRegistryNode } from './ContainerRegistry'; +import { LoadBalancerNode } from './LoadBalancer'; import { MySQLDBNode } from './MySQLDB'; +import { NatGatewayNode } from './NatGateway'; import { RegionGroup, SubnetGroup, VpcGroup } from './Networks'; -import { ServerNode } from './Server'; -import { ContainerRegistryNode } from './ContainerRegistry'; import { ObjectStorageNode } from './ObjectStorage'; +import { ServerNode } from './Server'; export const NcloudNodeFactory = (type: string) => { switch (type) { @@ -20,6 +21,8 @@ export const NcloudNodeFactory = (type: string) => { return ContainerRegistryNode; case 'object-storage': return ObjectStorageNode; + case 'nat-gateway': + return NatGatewayNode; default: { throw new Error(`Unknown type: ${type}`); } diff --git a/apps/client/src/types/index.ts b/apps/client/src/types/index.ts index 939ee9a8..32ee3eca 100644 --- a/apps/client/src/types/index.ts +++ b/apps/client/src/types/index.ts @@ -7,10 +7,13 @@ export type GridPoint = { col: number; row: number }; export type Size = { width: number; height: number; - offset?: number; - depth?: number; }; +export type Size3D = { + depth: number; + offset: number; +} & Size; + export type ViewBox = Point & Size; export type Bounds = Point & Size; @@ -21,10 +24,13 @@ export type Node = { point: Point; size: { '2d': Size; - '3d': Size; + '3d': Size3D; }; properties: { [id: string]: any }; connectors: { [key: string]: Point }; + filterConnectorTypes: { + [key: string]: string[]; + }; }; export type Edge = { diff --git a/apps/client/src/utils/index.ts b/apps/client/src/utils/index.ts index e0a1638e..3cf4a65b 100644 --- a/apps/client/src/utils/index.ts +++ b/apps/client/src/utils/index.ts @@ -3,7 +3,15 @@ import { GRID_3D_HEIGHT_SIZE, GRID_3D_WIDTH_SIZE, } from '@constants'; -import { ConnectorMap, Dimension, GridPoint, Node, Point } from '@types'; +import { + ConnectorMap, + Dimension, + GridPoint, + Node, + Point, + Size, + Size3D, +} from '@types'; export const getDistance = (point1: Point, point2: Point) => { return Math.sqrt((point1.x - point2.x) ** 2 + (point1.y - point2.y) ** 2); @@ -91,31 +99,95 @@ export const generateRandomRGB = () => { return `rgb(${r},${g},${b})`; }; +export const get3DBasePoint = (point: Point, size: Size3D) => { + const grid = screenToGrid3d({ + x: point.x, + y: point.y, + }); + + const ratio = size.width / 128; + const base = gridToScreen3d({ + col: grid.col + (ratio > 1 ? 1 : ratio), + row: grid.row, + }); + + return { + x: base.x, + y: point.y + size.height + (size.offset ?? 0) - 74, + }; +}; + +const calcConnectorFor3D = (node: Node) => { + const point = node.point; + const nodeSize = node.size['3d'] as Size3D; + const base = get3DBasePoint(point, nodeSize); + + const baseBottom = { + x: base.x, + y: base.y - nodeSize.offset - nodeSize.depth + 74, + }; + + const center = { + x: base.x, + y: point.y + (baseBottom.y - point.y) / 2, + }; + + const _ratio = nodeSize.width / 128; + const ratio = _ratio < 1 ? 1 : _ratio; + + const GRID_WIDTH_QUARTER_SIZE = GRID_3D_WIDTH_SIZE / 4; + const GRID_HEIGHT_QUARTER_SIZE = GRID_3D_HEIGHT_SIZE / 4; + const top = { + x: center.x + GRID_WIDTH_QUARTER_SIZE * ratio, + y: center.y - GRID_HEIGHT_QUARTER_SIZE * ratio, + }; + + const left = { + x: center.x - GRID_WIDTH_QUARTER_SIZE * ratio, + y: center.y - GRID_HEIGHT_QUARTER_SIZE * ratio, + }; + + const right = { + x: center.x + GRID_WIDTH_QUARTER_SIZE * ratio, + y: center.y + GRID_HEIGHT_QUARTER_SIZE * ratio, + }; + + const bottom = { + x: center.x - GRID_WIDTH_QUARTER_SIZE * ratio, + y: center.y + GRID_HEIGHT_QUARTER_SIZE * ratio, + }; + + return { + top, + right, + left, + bottom, + center, + }; +}; + export const getConnectorPoints = ( node: Node, dimension: Dimension, -): Omit => { +): ConnectorMap => { const point = node.point; - const { width, height } = node.size[dimension]; - const depth = GRID_3D_HEIGHT_SIZE / 2; - return { - top: { x: point.x + width / 2, y: point.y }, - right: - dimension === '2d' - ? { x: point.x + width, y: point.y + height / 2 } - : { - x: point.x + width, - y: point.y + (height - depth) / 2, - }, - left: - dimension === '2d' - ? { x: point.x, y: point.y + height / 2 } - : { - x: point.x, - y: point.y + (height - depth) / 2, - }, - bottom: { x: point.x + width / 2, y: point.y + height }, - }; + const nodeSize = node.size[dimension]; + const { width, height } = nodeSize; + + if (dimension === '2d') { + return { + top: { x: point.x + width / 2, y: point.y }, + right: { x: point.x + width, y: point.y + height / 2 }, + left: { x: point.x, y: point.y + height / 2 }, + bottom: { x: point.x + width / 2, y: point.y + height }, + }; + } + + const connector = calcConnectorFor3D(node) as any; + return node.filterConnectorTypes[dimension].reduce((acc: any, key: any) => { + acc[key] = connector[key]; + return acc; + }, {}); }; //INFO: 선분과 내적/외적 사이의 최단 거리를 계산(For Bend Point) @@ -169,3 +241,13 @@ export const findKeyByValue = ( ) => { return Object.keys(list).find((key) => list[key] === value); }; + +const calcIsoMatrixPoint = (point: Point) => { + const isoMatrix = new DOMMatrix() + .rotate(30) + .skewX(-30) + .scale(1, 0.8602) + .translate(point.x, point.y); + + return isoMatrix; // 결과 행렬 반환 +}; From 321aab562de6f1b1efd8fe497a1051d81ba67efb Mon Sep 17 00:00:00 2001 From: Geonhyuk Seo Date: Sun, 1 Dec 2024 20:37:00 +0900 Subject: [PATCH 060/124] =?UTF-8?q?=F0=9F=A4=96=20Refactor(infra):=20terra?= =?UTF-8?q?form=20output=20=EB=B3=80=EC=88=98=20=EC=84=9C=EB=B2=84=20?= =?UTF-8?q?=EB=B3=80=EC=88=98=EC=97=90=20=EA=B4=80=EB=A0=A8=ED=95=B4?= =?UTF-8?q?=EC=84=9C=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=ED=82=A4=20=EB=A7=90?= =?UTF-8?q?=EA=B3=A0=20=EC=84=9C=EB=B2=84=EC=97=90=20=EA=B4=80=ED=95=9C=20?= =?UTF-8?q?=EC=95=84=EC=9B=83=ED=92=8B=20=EB=B3=80=EC=88=98=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- infra/modules/server/outputs.tf | 11 ++++++++--- infra/modules/vpc_subnet/outputs.tf | 4 ++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/infra/modules/server/outputs.tf b/infra/modules/server/outputs.tf index 88b33bfa..603fd5ca 100644 --- a/infra/modules/server/outputs.tf +++ b/infra/modules/server/outputs.tf @@ -1,4 +1,9 @@ -output "servers_login_key" { - value = ncloud_login_key.servers_login_key.private_key - sensitive = true +output "server_publics" { + value = ncloud_server.public_servers[*] + description = "public server infos" +} + +output "server_privates" { + value = ncloud_server.private_servers[*] + description = "private server infos" } \ No newline at end of file diff --git a/infra/modules/vpc_subnet/outputs.tf b/infra/modules/vpc_subnet/outputs.tf index 35ea1168..01e6c8e3 100644 --- a/infra/modules/vpc_subnet/outputs.tf +++ b/infra/modules/vpc_subnet/outputs.tf @@ -4,11 +4,11 @@ output "vpc_id" { } output "public_subnets" { - description = "public subnets id" + description = "public subnets infos" value = ncloud_subnet.public_subnets[*] } output "private_subnets" { - description = "public subnets id" + description = "public subnets infos" value = ncloud_subnet.private_subnets[*] } \ No newline at end of file From 9bb71643656d7102d63452171dc860a06bd74cd8 Mon Sep 17 00:00:00 2001 From: p1n9d3v Date: Sun, 1 Dec 2024 21:14:03 +0900 Subject: [PATCH 061/124] =?UTF-8?q?=E2=9C=A8=20Feat(client):=20zoom=20?= =?UTF-8?q?=EC=A0=9C=ED=95=9C=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/client/src/components/Graph/index.tsx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/apps/client/src/components/Graph/index.tsx b/apps/client/src/components/Graph/index.tsx index 8518b7c7..75b49b84 100644 --- a/apps/client/src/components/Graph/index.tsx +++ b/apps/client/src/components/Graph/index.tsx @@ -15,7 +15,15 @@ export default ({ children }: PropsWithChildren) => { const isPanning = useRef(false); const startPoint = useRef({ x: 0, y: 0 }); const spaceActiveKey = useKey('space'); + const initialViewBox = { + x: 0, + y: 0, + width: svgRef.current?.clientWidth || 0, + height: svgRef.current?.clientHeight || 0, + }; + const MIN_ZOOM = 0.2; + const MAX_ZOOM = 1; const zoom = (wheelY: number, point: Point) => { if (!svgRef.current) return; @@ -23,6 +31,12 @@ export default ({ children }: PropsWithChildren) => { const cursorSvgPoint = getSvgPoint(svgRef.current, point); if (!cursorSvgPoint) return; + const currentZoom = initialViewBox.width / viewBox.width; + const newZoom = currentZoom * (1 / zoomFactor); + + if (newZoom < MIN_ZOOM || newZoom > MAX_ZOOM) { + return; + } dispatch({ type: 'SET_VIEWBOX', payload: { From 205e2cb09accd333665b6175fc41f48c9f21cc7d Mon Sep 17 00:00:00 2001 From: p1n9d3v Date: Sun, 1 Dec 2024 21:14:31 +0900 Subject: [PATCH 062/124] =?UTF-8?q?=F0=9F=A4=96=20Refactor(client):=20grou?= =?UTF-8?q?p=20svg=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/Group/ncloud/Rect3D.tsx | 55 +++++++++++++++++++ .../components/Group/ncloud/RegionGroup.tsx | 46 ++-------------- .../components/Group/ncloud/SubnetGroup.tsx | 53 +++--------------- .../src/components/Group/ncloud/VPCGroup.tsx | 53 +++--------------- 4 files changed, 76 insertions(+), 131 deletions(-) create mode 100644 apps/client/src/components/Group/ncloud/Rect3D.tsx diff --git a/apps/client/src/components/Group/ncloud/Rect3D.tsx b/apps/client/src/components/Group/ncloud/Rect3D.tsx new file mode 100644 index 00000000..bb248811 --- /dev/null +++ b/apps/client/src/components/Group/ncloud/Rect3D.tsx @@ -0,0 +1,55 @@ +import { Bounds } from '@types'; +import { screenToGrid2d, gridToScreen3d } from '@utils'; +import { ReactNode } from 'react'; + +type Props = { + children: ReactNode; + bounds: Bounds; + color: string; + [key: string]: any; +}; +export default ({ bounds, properties, color, children }: Props) => { + const topLeftGrid = screenToGrid2d({ x: 0, y: 0 }); + const topRightGrid = screenToGrid2d({ x: bounds.width, y: 0 }); + const bottomRightGrid = screenToGrid2d({ + x: bounds.width, + y: bounds.height, + }); + const bottomLeftGrid = screenToGrid2d({ x: 0, y: bounds.height }); + + const point1 = gridToScreen3d({ + col: topLeftGrid.col, + row: topLeftGrid.row, + }); + const point2 = gridToScreen3d({ + col: topRightGrid.col, + row: topRightGrid.row, + }); + const point3 = gridToScreen3d({ + col: bottomRightGrid.col, + row: bottomRightGrid.row, + }); + const point4 = gridToScreen3d({ + col: bottomLeftGrid.col, + row: bottomLeftGrid.row, + }); + + const points = ` + ${point1.x} ${point1.y}, + ${point2.x} ${point2.y}, + ${point3.x} ${point3.y}, + ${point4.x} ${point4.y} + `; + + return ( + <> + + {children} + + ); +}; diff --git a/apps/client/src/components/Group/ncloud/RegionGroup.tsx b/apps/client/src/components/Group/ncloud/RegionGroup.tsx index 0308d255..f583e427 100644 --- a/apps/client/src/components/Group/ncloud/RegionGroup.tsx +++ b/apps/client/src/components/Group/ncloud/RegionGroup.tsx @@ -1,8 +1,9 @@ import Text from '@components/Group/ncloud/Title'; import { useDimensionContext } from '@contexts/DimensionContext'; import { Bounds, Group } from '@types'; -import { generateRandomRGB, gridToScreen3d, screenToGrid2d } from '@utils'; +import { generateRandomRGB } from '@utils'; import { useMemo } from 'react'; +import Rect3D from './Rect3D'; interface Props extends Partial { color: string; @@ -10,52 +11,15 @@ interface Props extends Partial { } const Region3D = ({ bounds, properties, color }: Props) => { - const topLeftGrid = screenToGrid2d({ x: 0, y: 0 }); - const topRightGrid = screenToGrid2d({ x: bounds.width, y: 0 }); - const bottomRightGrid = screenToGrid2d({ - x: bounds.width, - y: bounds.height, - }); - const bottomLeftGrid = screenToGrid2d({ x: 0, y: bounds.height }); - - const point1 = gridToScreen3d({ - col: topLeftGrid.col + 1, - row: topLeftGrid.row, - }); - const point2 = gridToScreen3d({ - col: topRightGrid.col + 1, - row: topRightGrid.row, - }); - const point3 = gridToScreen3d({ - col: bottomRightGrid.col + 1, - row: bottomRightGrid.row, - }); - const point4 = gridToScreen3d({ - col: bottomLeftGrid.col + 1, - row: bottomLeftGrid.row, - }); - - const points = ` - ${point1.x} ${point1.y}, - ${point2.x} ${point2.y}, - ${point3.x} ${point3.y}, - ${point4.x} ${point4.y} - `; - return ( - <> - + - + ); }; const Region2D = ({ bounds, color, properties }: Props) => { + console.log(bounds); const points = `0 0, 0 ${bounds.height}, ${bounds.width} ${bounds.height}, ${bounds.width} 0`; return ( diff --git a/apps/client/src/components/Group/ncloud/SubnetGroup.tsx b/apps/client/src/components/Group/ncloud/SubnetGroup.tsx index 2d222447..8f43ff75 100644 --- a/apps/client/src/components/Group/ncloud/SubnetGroup.tsx +++ b/apps/client/src/components/Group/ncloud/SubnetGroup.tsx @@ -1,61 +1,24 @@ import Text from '@components/Group/ncloud/Title'; import { useDimensionContext } from '@contexts/DimensionContext'; import { Bounds, Group } from '@types'; -import { generateRandomRGB, gridToScreen3d, screenToGrid2d } from '@utils'; +import { calcIsoMatrixPoint, generateRandomRGB } from '@utils'; import { useMemo } from 'react'; +import Rect3D from './Rect3D'; interface Props extends Partial { color: string; bounds: Bounds; } -const Region3D = ({ bounds, properties, color }: Props) => { - const topLeftGrid = screenToGrid2d({ x: 0, y: 0 }); - const topRightGrid = screenToGrid2d({ x: bounds.width, y: 0 }); - const bottomRightGrid = screenToGrid2d({ - x: bounds.width, - y: bounds.height, - }); - const bottomLeftGrid = screenToGrid2d({ x: 0, y: bounds.height }); - - const point1 = gridToScreen3d({ - col: topLeftGrid.col + 1, - row: topLeftGrid.row, - }); - const point2 = gridToScreen3d({ - col: topRightGrid.col + 1, - row: topRightGrid.row, - }); - const point3 = gridToScreen3d({ - col: bottomRightGrid.col + 1, - row: bottomRightGrid.row, - }); - const point4 = gridToScreen3d({ - col: bottomLeftGrid.col + 1, - row: bottomLeftGrid.row, - }); - - const points = ` - ${point1.x} ${point1.y}, - ${point2.x} ${point2.y}, - ${point3.x} ${point3.y}, - ${point4.x} ${point4.y} - `; - +const Subnet3D = ({ bounds, properties, color }: Props) => { return ( - <> - + - + ); }; -const Region2D = ({ bounds, color, properties }: Props) => { +const Subnet2D = ({ bounds, color, properties }: Props) => { const points = `0 0, 0 ${bounds.height}, ${bounds.width} ${bounds.height}, ${bounds.width} 0`; return ( @@ -76,8 +39,8 @@ export default ({ bounds, properties }: Omit) => { const color = useMemo(() => generateRandomRGB(), []); return dimension === '2d' ? ( - + ) : ( - + ); }; diff --git a/apps/client/src/components/Group/ncloud/VPCGroup.tsx b/apps/client/src/components/Group/ncloud/VPCGroup.tsx index 2d222447..567a61c3 100644 --- a/apps/client/src/components/Group/ncloud/VPCGroup.tsx +++ b/apps/client/src/components/Group/ncloud/VPCGroup.tsx @@ -1,61 +1,24 @@ import Text from '@components/Group/ncloud/Title'; import { useDimensionContext } from '@contexts/DimensionContext'; import { Bounds, Group } from '@types'; -import { generateRandomRGB, gridToScreen3d, screenToGrid2d } from '@utils'; +import { calcIsoMatrixPoint, generateRandomRGB } from '@utils'; import { useMemo } from 'react'; +import Rect3D from './Rect3D'; interface Props extends Partial { color: string; bounds: Bounds; } -const Region3D = ({ bounds, properties, color }: Props) => { - const topLeftGrid = screenToGrid2d({ x: 0, y: 0 }); - const topRightGrid = screenToGrid2d({ x: bounds.width, y: 0 }); - const bottomRightGrid = screenToGrid2d({ - x: bounds.width, - y: bounds.height, - }); - const bottomLeftGrid = screenToGrid2d({ x: 0, y: bounds.height }); - - const point1 = gridToScreen3d({ - col: topLeftGrid.col + 1, - row: topLeftGrid.row, - }); - const point2 = gridToScreen3d({ - col: topRightGrid.col + 1, - row: topRightGrid.row, - }); - const point3 = gridToScreen3d({ - col: bottomRightGrid.col + 1, - row: bottomRightGrid.row, - }); - const point4 = gridToScreen3d({ - col: bottomLeftGrid.col + 1, - row: bottomLeftGrid.row, - }); - - const points = ` - ${point1.x} ${point1.y}, - ${point2.x} ${point2.y}, - ${point3.x} ${point3.y}, - ${point4.x} ${point4.y} - `; - +const VPC3D = ({ bounds, properties, color }: Props) => { return ( - <> - + - + ); }; -const Region2D = ({ bounds, color, properties }: Props) => { +const VPC2D = ({ bounds, color, properties }: Props) => { const points = `0 0, 0 ${bounds.height}, ${bounds.width} ${bounds.height}, ${bounds.width} 0`; return ( @@ -76,8 +39,8 @@ export default ({ bounds, properties }: Omit) => { const color = useMemo(() => generateRandomRGB(), []); return dimension === '2d' ? ( - + ) : ( - + ); }; From 1159f985c5d8391f619ba9bb9d06084e2a140287 Mon Sep 17 00:00:00 2001 From: p1n9d3v Date: Sun, 1 Dec 2024 21:15:36 +0900 Subject: [PATCH 063/124] =?UTF-8?q?=F0=9F=A4=96=20Refactor(client):=20?= =?UTF-8?q?=EC=A0=84=EC=B2=B4=EC=A0=81=EC=9C=BC=EB=A1=9C=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/client/mocks.ts | 4 +- apps/client/src/CloudGraph.tsx | 29 ++- .../src/components/Layout/Header/index.tsx | 7 +- .../src/components/Node/ncloud/NatGateway.tsx | 2 +- apps/client/src/contexts/DimensionContext.tsx | 13 +- .../src/contexts/GraphConetxt/index.tsx | 12 +- apps/client/src/helpers/edge.ts | 1 + apps/client/src/helpers/group.ts | 4 +- apps/client/src/hooks/useGraph.ts | 176 +++++++++++++----- apps/client/src/main.tsx | 10 +- .../client/src/models/ncloud/CloudFunction.ts | 4 - .../src/models/ncloud/ContainerRegistry.ts | 4 - apps/client/src/models/ncloud/LoadBalancer.ts | 4 - apps/client/src/models/ncloud/MySQLDB.ts | 4 - apps/client/src/models/ncloud/NatGateway.ts | 4 - .../client/src/models/ncloud/ObjectStorage.ts | 4 - apps/client/src/models/ncloud/Server.ts | 4 - apps/client/src/types/index.ts | 3 - apps/client/src/utils/index.ts | 15 +- 19 files changed, 183 insertions(+), 121 deletions(-) diff --git a/apps/client/mocks.ts b/apps/client/mocks.ts index f38cf6a9..e7815429 100644 --- a/apps/client/mocks.ts +++ b/apps/client/mocks.ts @@ -1,5 +1,5 @@ -// import { Group, Node } from '@types'; -// import { nanoid } from 'nanoid'; +import { Group, Node } from '@types'; +import { nanoid } from 'nanoid'; const CloudFunctionNode: Node = { id: `node-${nanoid()}`, diff --git a/apps/client/src/CloudGraph.tsx b/apps/client/src/CloudGraph.tsx index 1c48c769..8391b6ad 100644 --- a/apps/client/src/CloudGraph.tsx +++ b/apps/client/src/CloudGraph.tsx @@ -7,12 +7,13 @@ import GridBackground from '@components/GridBackground'; import Group from '@components/Group'; import Node from '@components/Node'; import { useEdgeContext } from '@contexts/EdgeContext'; +import { useGraphContext } from '@contexts/GraphConetxt'; import { useGroupContext } from '@contexts/GroupContext'; import { useNodeContext } from '@contexts/NodeContext'; import useConnection from '@hooks/useConnection'; import useGraph from '@hooks/useGraph'; import useSelection from '@hooks/useSelection'; -import { useEffect, useLayoutEffect } from 'react'; +import { useEffect, useLayoutEffect, useRef } from 'react'; export default () => { const { @@ -24,6 +25,11 @@ export default () => { const { state: { groups }, } = useGroupContext(); + const { + state: { viewBox }, + dispatch: graphDispatch, + } = useGraphContext(); + const { selectedNodeId, selectedEdge, @@ -36,14 +42,13 @@ export default () => { const { svgRef, - prevDimension, dimension, moveNode, addEdge, splitEdge, - updatedPointForDimension, moveBendingPointer, getGroupBounds, + updateNodePointForDimension, moveGroup, removeNode, removeEdge, @@ -59,6 +64,9 @@ export default () => { updateEdgeFn: addEdge, }); + const nodesRef = useRef(nodes); + const prevDimensionRef = useRef(dimension); + useEffect(() => { const handleContextMenu = (e: MouseEvent) => e.preventDefault(); const handleMouseDown = (e: MouseEvent) => { @@ -75,13 +83,20 @@ export default () => { }; }, []); - useLayoutEffect(() => { - if (dimension === prevDimension) return; + useEffect(() => { + prevDimensionRef.current = dimension; + }, [dimension]); + []; + + useEffect(() => { + nodesRef.current = nodes; + }, [nodes]); - updatedPointForDimension(); + useLayoutEffect(() => { + if (prevDimensionRef.current === dimension) return; + updateNodePointForDimension(dimension); }, [dimension]); - console.log(nodes); return ( diff --git a/apps/client/src/components/Layout/Header/index.tsx b/apps/client/src/components/Layout/Header/index.tsx index ce8bdf09..f6e486b8 100644 --- a/apps/client/src/components/Layout/Header/index.tsx +++ b/apps/client/src/components/Layout/Header/index.tsx @@ -44,7 +44,7 @@ const StyledIconButton = styled(IconButton)(({ theme }) => ({ export default () => { const { mode: themeMode, setMode: setThemeMode } = useColorScheme(); - const { dimension, toggleDimension } = useDimensionContext(); + const { dimension, changeDimension } = useDimensionContext(); const [openDrawer, setOpenDrawer] = useState(false); const [terraformCode, setTerraformCode] = useState(''); const { selectedResource } = useNCloud(); @@ -66,7 +66,6 @@ export default () => { } const Converter = new TerraformConverter(); - console.log(nodeProperties); Converter.addResourceFromJson([nodeProperties]); setTerraformCode(Converter.generate()); setOpenDrawer(true); @@ -95,7 +94,9 @@ export default () => { + changeDimension(dimension === '2d' ? '3d' : '2d') + } sx={{ height: '38px', }} diff --git a/apps/client/src/components/Node/ncloud/NatGateway.tsx b/apps/client/src/components/Node/ncloud/NatGateway.tsx index c3d00dea..35e18ad1 100644 --- a/apps/client/src/components/Node/ncloud/NatGateway.tsx +++ b/apps/client/src/components/Node/ncloud/NatGateway.tsx @@ -19,7 +19,7 @@ const Node3D = ({ properties }: Props) => { void; + changeDimension: (newDimension: Dimension) => void; }; const DimensionContext = createContext(null); export const DimensionProvider = ({ children }: { children: ReactNode }) => { const [dimension, setDimension] = useState('2d'); - const prevDimensionRef = useRef('2d'); - const toggleDimension = () => { - prevDimensionRef.current = dimension; - setDimension((prev) => (prev === '2d' ? '3d' : '2d')); + const changeDimension = (newDimension: Dimension) => { + if (dimension === newDimension) return; + setDimension(newDimension); }; return ( {children} diff --git a/apps/client/src/contexts/GraphConetxt/index.tsx b/apps/client/src/contexts/GraphConetxt/index.tsx index 7d0b2971..ce705a13 100644 --- a/apps/client/src/contexts/GraphConetxt/index.tsx +++ b/apps/client/src/contexts/GraphConetxt/index.tsx @@ -23,7 +23,13 @@ const initialState = { viewBox: { x: 0, y: 0, width: 0, height: 0 }, }; -export const GraphProvider = ({ children }: { children: ReactNode }) => { +export const GraphProvider = ({ + children, + initialZoomFactor = 2, +}: { + children: ReactNode; + initialZoomFactor: number; +}) => { const [state, dispatch] = useReducer(graphReducer, initialState); useLayoutEffect(() => { @@ -35,8 +41,8 @@ export const GraphProvider = ({ children }: { children: ReactNode }) => { payload: { x: state.viewBox.x || 0, y: state.viewBox.y || 0, - width: state.viewBox.width || svg.clientWidth, - height: state.viewBox.height || svg.clientHeight, + width: (state.viewBox.width || svg.clientWidth) * 2, + height: (state.viewBox.height || svg.clientHeight) * 2, }, }); }; diff --git a/apps/client/src/helpers/edge.ts b/apps/client/src/helpers/edge.ts index f5b624a2..aef3e6ba 100644 --- a/apps/client/src/helpers/edge.ts +++ b/apps/client/src/helpers/edge.ts @@ -112,6 +112,7 @@ export const updateNearestConnectorPair = ( const lastBendPoint = edge.bendingPoints[edge.bendingPoints.length - 1]; const bendConnector = generateBendConnector(lastBendPoint); + //TODO: 이름 변경 필요 const { movingConnector } = findNearestConnectorPair( movingConnectors, [bendConnector], diff --git a/apps/client/src/helpers/group.ts b/apps/client/src/helpers/group.ts index 371dbaad..cc3dc50f 100644 --- a/apps/client/src/helpers/group.ts +++ b/apps/client/src/helpers/group.ts @@ -35,9 +35,7 @@ export const computeBounds = ( width: maxX - minX + padding * 2, height: maxY - minY + padding * 2, }; - } - - if (dimension === '3d') { + } else { //2d nodes = nodes.map((node) => { const offset = getNodeOffsetForDimension( diff --git a/apps/client/src/hooks/useGraph.ts b/apps/client/src/hooks/useGraph.ts index 54f8b139..05491728 100644 --- a/apps/client/src/hooks/useGraph.ts +++ b/apps/client/src/hooks/useGraph.ts @@ -1,5 +1,6 @@ import { useDimensionContext } from '@contexts/DimensionContext'; import { useEdgeContext } from '@contexts/EdgeContext'; +import { useGraphContext } from '@contexts/GraphConetxt'; import { useGroupContext } from '@contexts/GroupContext'; import { useNodeContext } from '@contexts/NodeContext'; import { useSvgContext } from '@contexts/SvgContext'; @@ -11,7 +12,7 @@ import { import { computeBounds } from '@helpers/group'; import { adjustNodePointForDimension, alignNodePoint } from '@helpers/node'; import useSelection from '@hooks/useSelection'; -import { Connection, Edge, Group, Node, Point } from '@types'; +import { Connection, Dimension, Edge, Group, Node, Point } from '@types'; import { alignPoint2d, alignPoint3d, @@ -36,8 +37,13 @@ export default () => { dispatch: groupDispatch, } = useGroupContext(); + const { + state: { viewBox }, + dispatch: graphDispatch, + } = useGraphContext(); + const { clearSelection } = useSelection(); - const { dimension, prevDimension } = useDimensionContext(); + const { dimension } = useDimensionContext(); const { svgRef } = useSvgContext(); //INFO: Node @@ -114,32 +120,116 @@ export default () => { clearSelection(); }; - const updateNodePointForDimension = () => { - const updatedNodes = Object.entries(nodes).reduce((acc, [id, node]) => { - const adjustedPoint = adjustNodePointForDimension( - node.point, - node.size['3d'], - dimension, - ); + //TODO: Refactoring필요 + const updateNodePointForDimension = (dimension: Dimension) => { + //INFO: update node + const updatedNodes: Record = Object.entries(nodes).reduce( + (acc, [id, node]) => { + const adjustedPoint = adjustNodePointForDimension( + node.point, + node.size['3d'], + dimension, + ); + + const connectors = getConnectorPoints( + { ...node, point: adjustedPoint }, + dimension, + ); + + return { + ...acc, + [id]: { + ...node, + point: adjustedPoint, + connectors, + }, + }; + }, + {}, + ); - const connectors = getConnectorPoints( - { ...node, point: adjustedPoint }, - dimension, + //INFO:update edge + let updatedEdges = Object.entries(edges).reduce((acc, [id, edge]) => { + const adjustedBendingPoints = edge.bendingPoints.map((point) => + dimension === '2d' + ? convert3dTo2dPoint(point) + : convert2dTo3dPoint(point), ); + return { ...acc, [id]: { - ...node, - point: adjustedPoint, - connectors, + ...edge, + bendingPoints: adjustedBendingPoints, }, }; }, {}); + // const updatedEdgePairs = Object.entries(updatedNodes).reduce( + // (acc, [id, node]) => { + // const connectedEdges = Object.values(updatedEdges).filter( + // (edge) => edge.source.id === id || edge.target.id === id, + // ); + // const result = updateNearestConnectorPair( + // node, + // updatedNodes, + // connectedEdges as Edge[], + // ); + // return { + // ...acc, + // ...result, + // }; + // }, + // {}, + // ); + + //INFO: update ViewBox + const updatedNodesArr = Object.values(updatedNodes); + const minX = Math.min( + ...updatedNodesArr.map((node: Node) => node.point.x), + ); + const minY = Math.min( + ...updatedNodesArr.map((node: Node) => node.point.y), + ); + + const maxX = Math.max( + ...updatedNodesArr.map( + (node: Node) => node.point.x + node.size[dimension].width, + ), + ); + const maxY = Math.max( + ...updatedNodesArr.map( + (node: Node) => node.point.y + node.size[dimension].height, + ), + ); + + const nodesWidth = maxX - minX; + const nodesHeight = maxY - minY; + + const centerX = minX + nodesWidth / 2; + const centerY = minY + nodesHeight / 2; + + const paddingWidth = (viewBox.x + viewBox.width) / 2; + const paddingHeight = (viewBox.y + viewBox.height) / 2; + + graphDispatch({ + type: 'SET_VIEWBOX', + payload: { + ...viewBox, + x: centerX - paddingWidth, + y: centerY - paddingHeight, + }, + }); + nodeDispatch({ type: 'UPDATE_NODES', payload: updatedNodes, }); + + edgeDispatch({ + type: 'UPDATE_EDGES', + payload: updatedEdges, + }); }; //INFO: Edge @@ -257,29 +347,6 @@ export default () => { }); }; - const updateEdgePointForDimension = () => { - const updatedEdges = Object.entries(edges).reduce((acc, [id, edge]) => { - const adjustedBendingPoints = edge.bendingPoints.map((point) => - dimension === '2d' - ? convert3dTo2dPoint(point) - : convert2dTo3dPoint(point), - ); - - return { - ...acc, - [id]: { - ...edge, - bendingPoints: adjustedBendingPoints, - }, - }; - }, {}); - - edgeDispatch({ - type: 'UPDATE_EDGES', - payload: updatedEdges, - }); - }; - //INFO: Group const addGroup = (group: Group) => { @@ -379,14 +446,31 @@ export default () => { return computeBounds(innerNodes, dimension, 2); } - const childNodes = group.childGroupIds.map((childGroupId) => - recursiveGroupBounds(groups[childGroupId]), - ); + const childGroups = group.childGroupIds.map((childGroupId) => { + const bounds = recursiveGroupBounds(groups[childGroupId]); + //TODO: Group 생성시 bounds 넣어줘야될 것 같음 + return { + point: { + x: bounds.x, + y: bounds.y, + }, + size: { + '2d': { + width: bounds.width, + height: bounds.height, + }, + '3d': { + width: 0, + height: 0, + }, + }, + }; + }) as Node[]; const currentNodes = group.nodeIds.map((nodeId) => nodes[nodeId]); return computeBounds( - [...currentNodes, ...childNodes], + [...currentNodes, ...childGroups], dimension, 2, ); @@ -395,13 +479,7 @@ export default () => { return recursiveGroupBounds(group); }; - const updatedPointForDimension = () => { - updateNodePointForDimension(); - updateEdgePointForDimension(); - }; - return { - prevDimension, dimension, svgRef, nodes, @@ -413,6 +491,7 @@ export default () => { addEdge, removeEdge, splitEdge, + updateNodePointForDimension, moveBendingPointer, addGroup, addChildGroup, @@ -424,7 +503,6 @@ export default () => { moveGroup, removeGroup, removeNodeFromGroup, - updatedPointForDimension, excludeNodeFromGroup, }; }; diff --git a/apps/client/src/main.tsx b/apps/client/src/main.tsx index fa5241c8..a201aa48 100644 --- a/apps/client/src/main.tsx +++ b/apps/client/src/main.tsx @@ -15,9 +15,9 @@ createRoot(document.getElementById('root')!).render( - - - + + + @@ -28,8 +28,8 @@ createRoot(document.getElementById('root')!).render( - - + + , ); diff --git a/apps/client/src/models/ncloud/CloudFunction.ts b/apps/client/src/models/ncloud/CloudFunction.ts index 93acb5b6..3195f4e6 100644 --- a/apps/client/src/models/ncloud/CloudFunction.ts +++ b/apps/client/src/models/ncloud/CloudFunction.ts @@ -18,8 +18,4 @@ export const CloudFunctionNode: Node & { properties: { ...Networks, }, - filterConnectorTypes: { - '2d': ['top', 'right', 'bottom', 'left'], - '3d': ['top', 'right', 'bottom', 'left'], - }, }; diff --git a/apps/client/src/models/ncloud/ContainerRegistry.ts b/apps/client/src/models/ncloud/ContainerRegistry.ts index 92740103..4a9d3aba 100644 --- a/apps/client/src/models/ncloud/ContainerRegistry.ts +++ b/apps/client/src/models/ncloud/ContainerRegistry.ts @@ -18,10 +18,6 @@ export const ContainerRegistryNode: Node & { properties: { ...Networks, }, - filterConnectorTypes: { - '2d': ['top', 'right', 'bottom', 'left'], - '3d': ['top', 'right', 'bottom', 'left'], - }, }; export const ContainerRegistryRequiredFields = {}; diff --git a/apps/client/src/models/ncloud/LoadBalancer.ts b/apps/client/src/models/ncloud/LoadBalancer.ts index 5f807b6d..ba15c24b 100644 --- a/apps/client/src/models/ncloud/LoadBalancer.ts +++ b/apps/client/src/models/ncloud/LoadBalancer.ts @@ -23,10 +23,6 @@ export const LoadBalancerNode: Node & { networkType: undefined, subnetNoList: undefined, }, - filterConnectorTypes: { - '2d': ['top', 'right', 'bottom', 'left'], - '3d': ['top', 'right', 'bottom', 'left'], - }, }; export const LoadBalancerRequiredFields = { diff --git a/apps/client/src/models/ncloud/MySQLDB.ts b/apps/client/src/models/ncloud/MySQLDB.ts index 51c7a11e..364b49e9 100644 --- a/apps/client/src/models/ncloud/MySQLDB.ts +++ b/apps/client/src/models/ncloud/MySQLDB.ts @@ -16,10 +16,6 @@ export const MySQLDBNode: Node = { properties: { ...Networks, }, - filterConnectorTypes: { - '2d': ['top', 'right', 'bottom', 'left'], - '3d': ['top', 'right', 'bottom', 'left'], - }, }; export const MySQLDBRequiredFields = {}; diff --git a/apps/client/src/models/ncloud/NatGateway.ts b/apps/client/src/models/ncloud/NatGateway.ts index dcef2f28..cdcea61f 100644 --- a/apps/client/src/models/ncloud/NatGateway.ts +++ b/apps/client/src/models/ncloud/NatGateway.ts @@ -16,10 +16,6 @@ export const NatGatewayNode: Node = { properties: { ...Networks, }, - filterConnectorTypes: { - '2d': ['top', 'right', 'bottom', 'left'], - '3d': ['center'], - }, }; export const NatGatewayRequiredFields = {}; diff --git a/apps/client/src/models/ncloud/ObjectStorage.ts b/apps/client/src/models/ncloud/ObjectStorage.ts index 52a06b76..5d659bba 100644 --- a/apps/client/src/models/ncloud/ObjectStorage.ts +++ b/apps/client/src/models/ncloud/ObjectStorage.ts @@ -18,8 +18,4 @@ export const ObjectStorageNode: Node & { properties: { ...Networks, }, - filterConnectorTypes: { - '2d': ['top', 'right', 'bottom', 'left'], - '3d': ['top', 'right', 'bottom', 'left'], - }, }; diff --git a/apps/client/src/models/ncloud/Server.ts b/apps/client/src/models/ncloud/Server.ts index be56002d..86cc1e82 100644 --- a/apps/client/src/models/ncloud/Server.ts +++ b/apps/client/src/models/ncloud/Server.ts @@ -23,10 +23,6 @@ export const ServerNode: Node & { server_image_number: undefined, server_spec_code: undefined, }, - filterConnectorTypes: { - '2d': ['top', 'right', 'bottom', 'left'], - '3d': ['top', 'right', 'bottom', 'left'], - }, }; export const ServerRequiredFields = { diff --git a/apps/client/src/types/index.ts b/apps/client/src/types/index.ts index 32ee3eca..e9b927c7 100644 --- a/apps/client/src/types/index.ts +++ b/apps/client/src/types/index.ts @@ -28,9 +28,6 @@ export type Node = { }; properties: { [id: string]: any }; connectors: { [key: string]: Point }; - filterConnectorTypes: { - [key: string]: string[]; - }; }; export type Edge = { diff --git a/apps/client/src/utils/index.ts b/apps/client/src/utils/index.ts index 3cf4a65b..11901ba3 100644 --- a/apps/client/src/utils/index.ts +++ b/apps/client/src/utils/index.ts @@ -4,6 +4,7 @@ import { GRID_3D_WIDTH_SIZE, } from '@constants'; import { + Bounds, ConnectorMap, Dimension, GridPoint, @@ -89,7 +90,8 @@ export const convert3dTo2dPoint = (point: Point) => { }; export const convert2dTo3dPoint = (point: Point) => { - return gridToScreen3d(screenToGrid2d(point)); + const { col, row } = screenToGrid2d(point); + return gridToScreen3d({ col: col + 1, row }); }; export const generateRandomRGB = () => { @@ -158,11 +160,10 @@ const calcConnectorFor3D = (node: Node) => { }; return { - top, + top: base, right, left, bottom, - center, }; }; @@ -183,11 +184,7 @@ export const getConnectorPoints = ( }; } - const connector = calcConnectorFor3D(node) as any; - return node.filterConnectorTypes[dimension].reduce((acc: any, key: any) => { - acc[key] = connector[key]; - return acc; - }, {}); + return calcConnectorFor3D(node) as any; }; //INFO: 선분과 내적/외적 사이의 최단 거리를 계산(For Bend Point) @@ -242,7 +239,7 @@ export const findKeyByValue = ( return Object.keys(list).find((key) => list[key] === value); }; -const calcIsoMatrixPoint = (point: Point) => { +export const calcIsoMatrixPoint = (point: Point) => { const isoMatrix = new DOMMatrix() .rotate(30) .skewX(-30) From 8f5c65ca63bc692eaebfa04fcce0fd2b8a47acf0 Mon Sep 17 00:00:00 2001 From: p1n9d3v Date: Sun, 1 Dec 2024 21:19:25 +0900 Subject: [PATCH 064/124] =?UTF-8?q?=F0=9F=90=9E=20Fix(client):=20connector?= =?UTF-8?q?=20point=20=EC=98=A4=ED=83=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/client/src/utils/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/utils/index.ts b/apps/client/src/utils/index.ts index 11901ba3..ceb8983a 100644 --- a/apps/client/src/utils/index.ts +++ b/apps/client/src/utils/index.ts @@ -160,7 +160,7 @@ const calcConnectorFor3D = (node: Node) => { }; return { - top: base, + top, right, left, bottom, From 2318cc29dcd69f1748b5ba8f7810527348a6fd97 Mon Sep 17 00:00:00 2001 From: p1n9d3v Date: Sun, 1 Dec 2024 21:21:10 +0900 Subject: [PATCH 065/124] =?UTF-8?q?=F0=9F=92=84=20Style(client):=20title?= =?UTF-8?q?=20component=20offset=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/client/src/components/Group/ncloud/Title.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/components/Group/ncloud/Title.tsx b/apps/client/src/components/Group/ncloud/Title.tsx index 6ba19d10..f76a5310 100644 --- a/apps/client/src/components/Group/ncloud/Title.tsx +++ b/apps/client/src/components/Group/ncloud/Title.tsx @@ -24,7 +24,7 @@ export default ({ bounds, color, text }: Props) => { const rectWidth = fontSize * textLength; const rectHeight = 50; const rectY = 10; - const rectX = dimension === '2d' ? 10 : 90; + const rectX = 10; const matrix = convertToIsoMatrix(bounds.x, bounds.y).toString(); const centerX = rectWidth / 2 + rectX; From 35b9e01bf652f8bf94e3a41468eece9de7e20f11 Mon Sep 17 00:00:00 2001 From: Geonhyuk Seo Date: Mon, 2 Dec 2024 16:02:46 +0900 Subject: [PATCH 066/124] =?UTF-8?q?=F0=9F=90=9E=20Fix(docker-composes):=20?= =?UTF-8?q?=EB=A1=9C=EC=BB=AC=20=ED=99=98=EA=B2=BD=EC=9D=98=20=EB=8F=84?= =?UTF-8?q?=EC=BB=A4=20=EC=BB=B4=ED=8F=AC=EC=A6=88=20=EC=8A=A4=ED=81=AC?= =?UTF-8?q?=EB=A6=BD=ED=8A=B8=EC=97=90=EC=84=9C=20MYSQL=5FDATABASE?= =?UTF-8?q?=EC=97=90=20=EB=8C=80=ED=95=9C=20ENV=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/server/src/main.ts | 3 +-- docker-composes/cloud-canvas-local.yml | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/server/src/main.ts b/apps/server/src/main.ts index 95be9798..2c81382f 100644 --- a/apps/server/src/main.ts +++ b/apps/server/src/main.ts @@ -47,8 +47,7 @@ async function bootstrap() { allowedHeaders: ['Content-Type', 'Authorization', 'Accept'], credentials: true, }); - } - else { + } else { app.enableCors({ origin: 'https://cloudcanvas.kro.kr', methods: [ diff --git a/docker-composes/cloud-canvas-local.yml b/docker-composes/cloud-canvas-local.yml index 68e9db0f..4b209bd5 100644 --- a/docker-composes/cloud-canvas-local.yml +++ b/docker-composes/cloud-canvas-local.yml @@ -25,7 +25,6 @@ services: container_name: mysql environment: MYSQL_ROOT_PASSWORD: rootpassword - MYSQL_DATABASE: cloud_canvas MYSQL_USER: cloud_canvas_user MYSQL_PASSWORD: password ports: @@ -74,7 +73,7 @@ services: condition: service_healthy redis: condition: service_healthy - entrypoint: sh -c "cd apps/server && npx prisma migrate dev && npx prisma db seed && node ./dist/src/main.js" + entrypoint: sh -c "cd apps/server && npx prisma migrate dev && node ./dist/src/main.js" networks: - cloud-canvas-network restart: unless-stopped From a169c67110bb1859bef7a9b42d27c47febc9a4f3 Mon Sep 17 00:00:00 2001 From: p1n9d3v Date: Sun, 1 Dec 2024 21:31:57 +0900 Subject: [PATCH 067/124] =?UTF-8?q?=F0=9F=A4=96=20Refactor(client):=20cons?= =?UTF-8?q?ole.log=20=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/client/src/components/Group/ncloud/RegionGroup.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/client/src/components/Group/ncloud/RegionGroup.tsx b/apps/client/src/components/Group/ncloud/RegionGroup.tsx index f583e427..91c9b11f 100644 --- a/apps/client/src/components/Group/ncloud/RegionGroup.tsx +++ b/apps/client/src/components/Group/ncloud/RegionGroup.tsx @@ -19,7 +19,6 @@ const Region3D = ({ bounds, properties, color }: Props) => { }; const Region2D = ({ bounds, color, properties }: Props) => { - console.log(bounds); const points = `0 0, 0 ${bounds.height}, ${bounds.width} ${bounds.height}, ${bounds.width} 0`; return ( From a0edc8192ecbfb433d67245c81857a5f3a1330bc Mon Sep 17 00:00:00 2001 From: p1n9d3v Date: Mon, 2 Dec 2024 00:06:22 +0900 Subject: [PATCH 068/124] =?UTF-8?q?=E2=9C=A8=20Feat(client):=20fetch=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/client/src/App.tsx | 48 ++++++++++++++++++++- apps/client/src/apis/index.ts | 12 ++++++ apps/client/src/hooks/useFetch.ts | 71 +++++++++++++++++++++++++++++++ apps/client/src/hooks/usePost.ts | 46 -------------------- apps/client/src/main.tsx | 23 +--------- 5 files changed, 130 insertions(+), 70 deletions(-) create mode 100644 apps/client/src/apis/index.ts create mode 100644 apps/client/src/hooks/useFetch.ts delete mode 100644 apps/client/src/hooks/usePost.ts diff --git a/apps/client/src/App.tsx b/apps/client/src/App.tsx index 4058d78a..d6e49edf 100644 --- a/apps/client/src/App.tsx +++ b/apps/client/src/App.tsx @@ -3,11 +3,55 @@ import Header from '@components/Layout/Header'; import Sidebar from '@components/Layout/Sidebar'; import NetworksBar from '@components/NCloud/NetworksBar/index'; import PropertiesBar from '@components/NCloud/PropertiesBar'; +import { DimensionProvider } from '@contexts/DimensionContext'; +import { EdgeProvider } from '@contexts/EdgeContext'; +import { GraphProvider } from '@contexts/GraphConetxt'; +import { GroupProvider } from '@contexts/GroupContext'; +import { NodeProvider } from '@contexts/NodeContext'; +import { SelectionProvider } from '@contexts/SelectionContext'; +import { SvgProvider } from '@contexts/SvgContext'; +import useFetch from '@hooks/useFetch'; import Box from '@mui/material/Box'; +import { ReactNode, useEffect, useLayoutEffect } from 'react'; +import { urls } from './apis'; + +const BASE_URL = 'http://localhost:3000'; +const CloudGraphProvider = ({ children }: { children: ReactNode }) => { + const { execute: postLogin } = useFetch(urls(BASE_URL, 'login'), { + method: 'POST', + }); + const { data, execute } = useFetch(urls(BASE_URL, 'privateArchi', 1), { + method: 'GET', + }); + + useLayoutEffect(() => { + postLogin({}); + }, []); + + console.log(data); + + return ( + + + + + + + + {children} + + + + + + + + ); +}; function App() { return ( - <> + - + ); } diff --git a/apps/client/src/apis/index.ts b/apps/client/src/apis/index.ts new file mode 100644 index 00000000..2e73c716 --- /dev/null +++ b/apps/client/src/apis/index.ts @@ -0,0 +1,12 @@ +export const URLS = { + login: 'auth/login', + privateArchi: (id: string) => `private-architectures/${id}`, +}; +export const urls = (domain: string, path: keyof typeof URLS, slug?: any) => { + const urls = URLS[path]; + if (typeof urls === 'function') { + return `${domain}/${urls(slug)}`; + } + + return `${domain}/${urls}`; +}; diff --git a/apps/client/src/hooks/useFetch.ts b/apps/client/src/hooks/useFetch.ts new file mode 100644 index 00000000..c2f62a1f --- /dev/null +++ b/apps/client/src/hooks/useFetch.ts @@ -0,0 +1,71 @@ +import { useState, useEffect } from 'react'; + +type HttpMethod = 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE'; + +interface UseFetchOptions { + method?: HttpMethod; + headers?: HeadersInit; + body?: any; + trigger?: any; + credentials?: RequestCredentials; +} + +interface UseFetchResult { + data: T | null; + loading: boolean; + error: Error | null; + execute: (body?: any) => Promise; +} + +function useFetch( + url: string, + options: UseFetchOptions = {}, +): UseFetchResult { + const { method = 'GET', headers, body, trigger, credentials } = options; + + const [data, setData] = useState(null); + const [loading, setLoading] = useState(method === 'GET'); + const [error, setError] = useState(null); + + const execute = async (executeBody?: any) => { + setLoading(true); + setError(null); + + try { + const response = await fetch(url, { + method, + headers: { + 'Content-Type': 'application/json', + ...headers, + }, + body: executeBody + ? JSON.stringify(executeBody) + : body + ? JSON.stringify(body) + : null, + credentials: credentials ?? 'include', + }); + + if (!response.ok) { + throw new Error(`HTTP 오류! 상태 코드: ${response.status}`); + } + + const result: T = await response.json(); + setData(result); + } catch (err: any) { + setError(err); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + if (method === 'GET') { + execute(); + } + }, [url, method, trigger]); + + return { data, loading, error, execute }; +} + +export default useFetch; diff --git a/apps/client/src/hooks/usePost.ts b/apps/client/src/hooks/usePost.ts deleted file mode 100644 index 047e78ce..00000000 --- a/apps/client/src/hooks/usePost.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { useState } from 'react'; - -type State = { - data: T | null; - loading: boolean; - error: string | null; - postData: (body: any) => Promise; -}; - -const usePost = (url: string): State => { - const [data, setData] = useState(null); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); - - const postData = async (body: any) => { - setLoading(true); - setError(null); - - try { - const response = await fetch(url, { - method: 'POST', - // headers: { - // 'Content-Type': 'application/json', - // }, - credentials: 'include', - // body: JSON.stringify(body), - }); - - if (!response.ok) { - const errorMessage = await response.text(); - throw new Error(errorMessage || 'Error posting data'); - } - - const responseData = await response.json(); - setData(responseData); - } catch (err: any) { - setError(err.message || 'Unexpected Error'); - } finally { - setLoading(false); - } - }; - - return { data, loading, error, postData }; -}; - -export default usePost; diff --git a/apps/client/src/main.tsx b/apps/client/src/main.tsx index a201aa48..de28a04c 100644 --- a/apps/client/src/main.tsx +++ b/apps/client/src/main.tsx @@ -1,10 +1,3 @@ -import { DimensionProvider } from '@contexts/DimensionContext'; -import { EdgeProvider } from '@contexts/EdgeContext/index.tsx'; -import { GraphProvider } from '@contexts/GraphConetxt'; -import { GroupProvider } from '@contexts/GroupContext/index.tsx'; -import { NodeProvider } from '@contexts/NodeContext/index.tsx'; -import { SelectionProvider } from '@contexts/SelectionContext/index.tsx'; -import { SvgProvider } from '@contexts/SvgContext.tsx'; import { CssBaseline, ThemeProvider } from '@mui/material'; import theme from '@theme'; import { StrictMode } from 'react'; @@ -15,21 +8,7 @@ createRoot(document.getElementById('root')!).render( - - - - - - - - - - - - - - - + , ); From ebd2803e727602b16b76cfbfbacc9161805dee24 Mon Sep 17 00:00:00 2001 From: p1n9d3v Date: Mon, 2 Dec 2024 00:07:07 +0900 Subject: [PATCH 069/124] =?UTF-8?q?=F0=9F=A4=96=20Refactor(client):=20rect?= =?UTF-8?q?=20col=20=EC=A1=B0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/client/src/components/Group/ncloud/Rect3D.tsx | 8 ++++---- apps/client/src/components/Node/ncloud/NatGateway.tsx | 2 +- apps/client/src/utils/index.ts | 3 +-- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/apps/client/src/components/Group/ncloud/Rect3D.tsx b/apps/client/src/components/Group/ncloud/Rect3D.tsx index bb248811..94844e63 100644 --- a/apps/client/src/components/Group/ncloud/Rect3D.tsx +++ b/apps/client/src/components/Group/ncloud/Rect3D.tsx @@ -18,19 +18,19 @@ export default ({ bounds, properties, color, children }: Props) => { const bottomLeftGrid = screenToGrid2d({ x: 0, y: bounds.height }); const point1 = gridToScreen3d({ - col: topLeftGrid.col, + col: topLeftGrid.col + 1, row: topLeftGrid.row, }); const point2 = gridToScreen3d({ - col: topRightGrid.col, + col: topRightGrid.col + 1, row: topRightGrid.row, }); const point3 = gridToScreen3d({ - col: bottomRightGrid.col, + col: bottomRightGrid.col + 1, row: bottomRightGrid.row, }); const point4 = gridToScreen3d({ - col: bottomLeftGrid.col, + col: bottomLeftGrid.col + 1, row: bottomLeftGrid.row, }); diff --git a/apps/client/src/components/Node/ncloud/NatGateway.tsx b/apps/client/src/components/Node/ncloud/NatGateway.tsx index 35e18ad1..c3d00dea 100644 --- a/apps/client/src/components/Node/ncloud/NatGateway.tsx +++ b/apps/client/src/components/Node/ncloud/NatGateway.tsx @@ -19,7 +19,7 @@ const Node3D = ({ properties }: Props) => { { }; export const convert2dTo3dPoint = (point: Point) => { - const { col, row } = screenToGrid2d(point); - return gridToScreen3d({ col: col + 1, row }); + return gridToScreen3d(screenToGrid2d(point)); }; export const generateRandomRGB = () => { From a4172d4ad4a978806b49ce0e37dbc3f37cc63f2d Mon Sep 17 00:00:00 2001 From: p1n9d3v Date: Mon, 2 Dec 2024 00:07:30 +0900 Subject: [PATCH 070/124] =?UTF-8?q?=F0=9F=90=9E=20Fix(client):=20docker=20?= =?UTF-8?q?seed=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-composes/cloud-canvas-local.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-composes/cloud-canvas-local.yml b/docker-composes/cloud-canvas-local.yml index 68e9db0f..06d4b8cc 100644 --- a/docker-composes/cloud-canvas-local.yml +++ b/docker-composes/cloud-canvas-local.yml @@ -74,7 +74,7 @@ services: condition: service_healthy redis: condition: service_healthy - entrypoint: sh -c "cd apps/server && npx prisma migrate dev && npx prisma db seed && node ./dist/src/main.js" + entrypoint: sh -c "cd apps/server && npx prisma migrate dev && node ./dist/src/main.js" networks: - cloud-canvas-network restart: unless-stopped From 6c43b45e83c5799a1d53d2cde92e3e23ebfbf30f Mon Sep 17 00:00:00 2001 From: p1n9d3v Date: Mon, 2 Dec 2024 00:27:24 +0900 Subject: [PATCH 071/124] =?UTF-8?q?=F0=9F=A4=96=20Refactor(client):=20svg?= =?UTF-8?q?=20`-`=EC=9E=88=EB=8A=94=20=EC=86=8D=EC=84=B1=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/Group/ncloud/Title.tsx | 2 +- apps/client/src/components/Node/index.tsx | 2 -- .../Node/ncloud/ContainerRegistry.tsx | 6 ++--- .../components/Node/ncloud/LoadBalancer.tsx | 10 +++---- .../Node/ncloud/LoadBalancerNode.tsx | 10 +++---- .../src/components/Node/ncloud/NatGateway.tsx | 26 +++++++++---------- 6 files changed, 27 insertions(+), 29 deletions(-) diff --git a/apps/client/src/components/Group/ncloud/Title.tsx b/apps/client/src/components/Group/ncloud/Title.tsx index f76a5310..6ba19d10 100644 --- a/apps/client/src/components/Group/ncloud/Title.tsx +++ b/apps/client/src/components/Group/ncloud/Title.tsx @@ -24,7 +24,7 @@ export default ({ bounds, color, text }: Props) => { const rectWidth = fontSize * textLength; const rectHeight = 50; const rectY = 10; - const rectX = 10; + const rectX = dimension === '2d' ? 10 : 90; const matrix = convertToIsoMatrix(bounds.x, bounds.y).toString(); const centerX = rectWidth / 2 + rectX; diff --git a/apps/client/src/components/Node/index.tsx b/apps/client/src/components/Node/index.tsx index 7db83c70..d2b78e9c 100644 --- a/apps/client/src/components/Node/index.tsx +++ b/apps/client/src/components/Node/index.tsx @@ -21,8 +21,6 @@ const nodeFactory = (node: Node) => { return ; case 'load-balancer': return ; - case 'object-storage': - return ; case 'container-registry': return ; case 'nat-gateway': diff --git a/apps/client/src/components/Node/ncloud/ContainerRegistry.tsx b/apps/client/src/components/Node/ncloud/ContainerRegistry.tsx index c80e251b..5d215160 100644 --- a/apps/client/src/components/Node/ncloud/ContainerRegistry.tsx +++ b/apps/client/src/components/Node/ncloud/ContainerRegistry.tsx @@ -91,7 +91,7 @@ const Node2D = ({ properties }: Props) => { @@ -118,7 +118,7 @@ const Node2D = ({ properties }: Props) => { > ECS Cluster @@ -130,7 +130,7 @@ const Node2D = ({ properties }: Props) => { diff --git a/apps/client/src/components/Node/ncloud/LoadBalancer.tsx b/apps/client/src/components/Node/ncloud/LoadBalancer.tsx index c2a113f2..9ae23f1d 100644 --- a/apps/client/src/components/Node/ncloud/LoadBalancer.tsx +++ b/apps/client/src/components/Node/ncloud/LoadBalancer.tsx @@ -21,7 +21,7 @@ const Node3D = ({ properties }: Props) => { fill="none" stroke="#a4c4e3" d="M.955 37.877 32.333 55.8h32.334l31.378-17.923M32.333 55.8v36.211M64.667 55.8v36.574" - stroke-width="1.04" + strokeWidth="1.04" > { fill="#ffffff" stroke="#ffffff" d="m46.993 26.089 6.654-1.146-4.324 5.168c.189-1.691-.757-3.313-2.33-4.022Z" - stroke-width=".36" + strokeWidth=".36" > { fill="#ffffff" stroke="#ffffff" d="m39.76 23.812 4.676-4.854-.405 6.716c-.855-1.474-2.582-2.221-4.271-1.862Z" - stroke-width=".36" + strokeWidth=".36" > { fill="#ffffff" stroke="#ffffff" d="m56.68 28.957 6.608 1.387-5.936 3.209c.804-1.503.526-3.358-.672-4.596Z" - stroke-width=".36" + strokeWidth=".36" > ); diff --git a/apps/client/src/components/Node/ncloud/LoadBalancerNode.tsx b/apps/client/src/components/Node/ncloud/LoadBalancerNode.tsx index c2a113f2..9ae23f1d 100644 --- a/apps/client/src/components/Node/ncloud/LoadBalancerNode.tsx +++ b/apps/client/src/components/Node/ncloud/LoadBalancerNode.tsx @@ -21,7 +21,7 @@ const Node3D = ({ properties }: Props) => { fill="none" stroke="#a4c4e3" d="M.955 37.877 32.333 55.8h32.334l31.378-17.923M32.333 55.8v36.211M64.667 55.8v36.574" - stroke-width="1.04" + strokeWidth="1.04" > { fill="#ffffff" stroke="#ffffff" d="m46.993 26.089 6.654-1.146-4.324 5.168c.189-1.691-.757-3.313-2.33-4.022Z" - stroke-width=".36" + strokeWidth=".36" > { fill="#ffffff" stroke="#ffffff" d="m39.76 23.812 4.676-4.854-.405 6.716c-.855-1.474-2.582-2.221-4.271-1.862Z" - stroke-width=".36" + strokeWidth=".36" > { fill="#ffffff" stroke="#ffffff" d="m56.68 28.957 6.608 1.387-5.936 3.209c.804-1.503.526-3.358-.672-4.596Z" - stroke-width=".36" + strokeWidth=".36" > ); diff --git a/apps/client/src/components/Node/ncloud/NatGateway.tsx b/apps/client/src/components/Node/ncloud/NatGateway.tsx index c3d00dea..1129fcc0 100644 --- a/apps/client/src/components/Node/ncloud/NatGateway.tsx +++ b/apps/client/src/components/Node/ncloud/NatGateway.tsx @@ -21,54 +21,54 @@ const Node3D = ({ properties }: Props) => { fill="#ccc" stroke="#a4c4e3" d="M38.23 22.751v53.738l29.756-16.317v-54.6Z" - fill-rule="evenodd" - stroke-linejoin="round" - stroke-width="1.011" + fillRule="evenodd" + strokeLinejoin="round" + strokeWidth="1.011" > Date: Mon, 2 Dec 2024 00:27:50 +0900 Subject: [PATCH 072/124] =?UTF-8?q?=F0=9F=9A=9A=20Setting(client):=20prod,?= =?UTF-8?q?=20dev=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/client/.env.development | 2 ++ apps/client/.env.production | 2 ++ apps/client/package.json | 9 +++++---- apps/client/src/App.tsx | 5 +++-- apps/client/src/vite-env.d.ts | 1 + 5 files changed, 13 insertions(+), 6 deletions(-) create mode 100644 apps/client/.env.development create mode 100644 apps/client/.env.production diff --git a/apps/client/.env.development b/apps/client/.env.development new file mode 100644 index 00000000..7a4131e6 --- /dev/null +++ b/apps/client/.env.development @@ -0,0 +1,2 @@ +VITE_API_URL=http://localhost:3000 +VITE_MODE=dev diff --git a/apps/client/.env.production b/apps/client/.env.production new file mode 100644 index 00000000..a02ef3a3 --- /dev/null +++ b/apps/client/.env.production @@ -0,0 +1,2 @@ +VITE_API_URL=https://api.cloudcanvas.kro.kr +VITE_MODE=prod diff --git a/apps/client/package.json b/apps/client/package.json index 5f6fcdb7..185f7f83 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -6,10 +6,11 @@ "keywords": [], "type": "module", "scripts": { - "start": "serve dist", - "dev": "vite", - "build": "tsc -b && vite build", - "clean": "rm -rf dist" + "dev": "vite --mode development", + "build:prod": "tsc -b && vite build --mode production", + "build:dev": "tsc -b && vite build --mode development", + "clean": "rm -rf dist", + "preview": "vite preview" }, "dependencies": { "@emotion/react": "^11.13.3", diff --git a/apps/client/src/App.tsx b/apps/client/src/App.tsx index d6e49edf..ab51e072 100644 --- a/apps/client/src/App.tsx +++ b/apps/client/src/App.tsx @@ -12,10 +12,11 @@ import { SelectionProvider } from '@contexts/SelectionContext'; import { SvgProvider } from '@contexts/SvgContext'; import useFetch from '@hooks/useFetch'; import Box from '@mui/material/Box'; -import { ReactNode, useEffect, useLayoutEffect } from 'react'; +import { ReactNode, useLayoutEffect } from 'react'; import { urls } from './apis'; -const BASE_URL = 'http://localhost:3000'; +const BASE_URL = import.meta.env.VITE_API_URL; + const CloudGraphProvider = ({ children }: { children: ReactNode }) => { const { execute: postLogin } = useFetch(urls(BASE_URL, 'login'), { method: 'POST', diff --git a/apps/client/src/vite-env.d.ts b/apps/client/src/vite-env.d.ts index e69de29b..d5e866e4 100644 --- a/apps/client/src/vite-env.d.ts +++ b/apps/client/src/vite-env.d.ts @@ -0,0 +1 @@ +/// From ac8fd77b29240336f1bfc2d4b532d47302076998 Mon Sep 17 00:00:00 2001 From: p1n9d3v Date: Mon, 2 Dec 2024 16:15:08 +0900 Subject: [PATCH 073/124] =?UTF-8?q?=F0=9F=A4=96=20Refactor(client):=20edge?= =?UTF-8?q?=20=EC=97=B0=EA=B2=B0=20=EB=B0=A9=EB=B2=95=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/client/src/App.tsx | 36 ++-- apps/client/src/CloudGraph.tsx | 159 ++++++++++-------- .../src/components/Connectors/Connector.tsx | 4 +- .../src/components/Connectors/index.tsx | 56 +----- apps/client/src/components/Graph/index.tsx | 34 +++- .../src/components/Layout/Header/index.tsx | 29 ++++ apps/client/src/components/NodeActions.tsx | 90 ++++++++++ apps/client/src/components/SpeedDial.tsx | 77 +++++++++ .../client/src/contexts/EdgeContext/index.tsx | 17 +- .../src/contexts/GroupContext/index.tsx | 23 ++- .../client/src/contexts/NodeContext/index.tsx | 16 +- apps/client/src/hooks/useConnection.ts | 1 + apps/client/src/hooks/useGraph.ts | 1 + apps/client/src/main.tsx | 6 +- .../client/src/models/ncloud/CloudFunction.ts | 2 +- .../src/models/ncloud/ContainerRegistry.ts | 2 +- apps/client/src/models/ncloud/LoadBalancer.ts | 2 +- apps/client/src/models/ncloud/NatGateway.ts | 2 +- .../client/src/models/ncloud/ObjectStorage.ts | 2 +- apps/client/src/models/ncloud/Server.ts | 2 +- apps/client/src/types/index.ts | 1 - apps/client/src/utils/index.ts | 30 +++- 22 files changed, 422 insertions(+), 170 deletions(-) create mode 100644 apps/client/src/components/NodeActions.tsx create mode 100644 apps/client/src/components/SpeedDial.tsx diff --git a/apps/client/src/App.tsx b/apps/client/src/App.tsx index ab51e072..be29247a 100644 --- a/apps/client/src/App.tsx +++ b/apps/client/src/App.tsx @@ -17,11 +17,20 @@ import { urls } from './apis'; const BASE_URL = import.meta.env.VITE_API_URL; -const CloudGraphProvider = ({ children }: { children: ReactNode }) => { - const { execute: postLogin } = useFetch(urls(BASE_URL, 'login'), { +export const CloudGraphProvider = ({ children }: { children: ReactNode }) => { + const { + error: errorLogin, + loading: loadingLogin, + execute: postLogin, + } = useFetch(urls(BASE_URL, 'login'), { method: 'POST', }); - const { data, execute } = useFetch(urls(BASE_URL, 'privateArchi', 1), { + const { + error: errorArchi, + loading: loadingArchi, + data, + execute, + } = useFetch(urls(BASE_URL, 'privateArchi', 1), { method: 'GET', }); @@ -29,15 +38,18 @@ const CloudGraphProvider = ({ children }: { children: ReactNode }) => { postLogin({}); }, []); - console.log(data); + if (loadingArchi || loadingLogin) return null; + if (errorArchi || errorLogin) return
    error
    ; return ( - - - + + + {children} @@ -50,9 +62,9 @@ const CloudGraphProvider = ({ children }: { children: ReactNode }) => { ); }; -function App() { +export const App = () => { return ( - + <> - + ); -} - -export default App; +}; diff --git a/apps/client/src/CloudGraph.tsx b/apps/client/src/CloudGraph.tsx index 8391b6ad..21f472e1 100644 --- a/apps/client/src/CloudGraph.tsx +++ b/apps/client/src/CloudGraph.tsx @@ -13,7 +13,10 @@ import { useNodeContext } from '@contexts/NodeContext'; import useConnection from '@hooks/useConnection'; import useGraph from '@hooks/useGraph'; import useSelection from '@hooks/useSelection'; -import { useEffect, useLayoutEffect, useRef } from 'react'; +import { Box } from '@mui/material'; +import { useEffect, useLayoutEffect, useMemo, useRef } from 'react'; +import SpeedDial from '@components/SpeedDial'; +import NodeActions from '@components/NodeActions'; export default () => { const { @@ -70,18 +73,30 @@ export default () => { useEffect(() => { const handleContextMenu = (e: MouseEvent) => e.preventDefault(); const handleMouseDown = (e: MouseEvent) => { - if (!(e.target as HTMLElement).closest('.graph-ignore-select')) + if ( + !(e.target as HTMLElement).closest('.graph-ignore-select') || + isConnecting + ) clearSelection(); }; + const handleClick = (e: MouseEvent) => { + if (isConnecting) { + closeConnection(); + clearSelection(); + document.body.style.cursor = 'default'; + } + }; document.addEventListener('contextmenu', handleContextMenu); document.addEventListener('mousedown', handleMouseDown); + document.addEventListener('click', handleClick); return () => { document.removeEventListener('contextmenu', handleContextMenu); document.removeEventListener('mousedown', handleMouseDown); + document.removeEventListener('click', handleClick); }; - }, []); + }, [isConnecting, selectedNodeId]); useEffect(() => { prevDimensionRef.current = dimension; @@ -98,76 +113,82 @@ export default () => { }, [dimension]); return ( - - - {Object.values(groups).map((group) => ( - - ))} - {Object.values(nodes).map((node) => ( - - - + + + {Object.values(groups).map((group) => ( + - - ))} - {connection && ( - - )} - - {edges && - Object.values(edges).map((edge) => ( - - - {edge.bendingPoints.map((point, index) => ( - - moveBendingPointer(edge.id, index, newPoint) + ))} + {edges && + Object.values(edges).map((edge) => ( + + - ))} + {edge.bendingPoints.map((point, index) => ( + + moveBendingPointer( + edge.id, + index, + newPoint, + ) + } + /> + ))} + + ))} + {connection && ( + + )} + {Object.values(nodes).map((node) => ( + + + ))} - +
    + {selectedNodeId && ( + + )} + ); }; diff --git a/apps/client/src/components/Connectors/Connector.tsx b/apps/client/src/components/Connectors/Connector.tsx index 0ad2b247..7adfe19e 100644 --- a/apps/client/src/components/Connectors/Connector.tsx +++ b/apps/client/src/components/Connectors/Connector.tsx @@ -4,10 +4,9 @@ import { Point } from '@types'; type Props = { visible: boolean; point: Point; - onMouseDown: (e: React.MouseEvent) => void; }; -export default ({ point, visible, onMouseDown }: Props) => { +export default ({ point, visible }: Props) => { const theme = useTheme(); return ( @@ -19,7 +18,6 @@ export default ({ point, visible, onMouseDown }: Props) => { style={{ visibility: visible ? 'visible' : 'hidden', }} - onMouseDown={onMouseDown} /> ); }; diff --git a/apps/client/src/components/Connectors/index.tsx b/apps/client/src/components/Connectors/index.tsx index 4cb94ea4..6072ce2a 100644 --- a/apps/client/src/components/Connectors/index.tsx +++ b/apps/client/src/components/Connectors/index.tsx @@ -1,68 +1,18 @@ import Connector from '@components/Connectors/Connector'; -import { Connection, Node, Point } from '@types'; -import { useEffect } from 'react'; +import { Node } from '@types'; type Props = { node: Node; - isSelected: boolean; - isConnecting: boolean; - onOpenConnection: (from: Connection) => void; - onConnectConnection: (point: Point) => void; - onCloseConnection: () => void; }; -export default ({ - node, - isSelected, - isConnecting, - onOpenConnection, - onConnectConnection, - onCloseConnection, -}: Props) => { - const handleMouseDown = ( - e: React.MouseEvent, - connectorType: string, - point: Point, - ) => { - e.stopPropagation(); - onOpenConnection({ - id: node.id, - connectorType, - point, - }); - document.body.style.cursor = 'move'; - }; - - const handleMouseMove = (e: MouseEvent) => { - const { clientX, clientY } = e; - onConnectConnection({ x: clientX, y: clientY }); - }; - - const handleCloseConnection = () => { - onCloseConnection(); - document.body.style.cursor = 'default'; - }; - - useEffect(() => { - if (isConnecting) { - document.addEventListener('mousemove', handleMouseMove); - document.addEventListener('mouseup', handleCloseConnection); - } - - return () => { - document.removeEventListener('mousemove', handleMouseMove); - document.removeEventListener('mouseup', handleCloseConnection); - }; - }, [isConnecting]); - +export default ({ node }: Props) => { return ( <> {Object.entries(node.connectors).map(([type, point]) => ( handleMouseDown(e, type, point)} + visible={false} /> ))} diff --git a/apps/client/src/components/Graph/index.tsx b/apps/client/src/components/Graph/index.tsx index 75b49b84..860152e5 100644 --- a/apps/client/src/components/Graph/index.tsx +++ b/apps/client/src/components/Graph/index.tsx @@ -3,7 +3,7 @@ import { useSvgContext } from '@contexts/SvgContext'; import useKey from '@hooks/useKey'; import { Point } from '@types'; import { getSvgPoint } from '@utils'; -import { PropsWithChildren, useRef } from 'react'; +import { PropsWithChildren, useRef, useEffect } from 'react'; export default ({ children }: PropsWithChildren) => { const { svgRef } = useSvgContext(); @@ -24,6 +24,14 @@ export default ({ children }: PropsWithChildren) => { const MIN_ZOOM = 0.2; const MAX_ZOOM = 1; + + const zoomEndTimer = useRef | null>(null); + + const handleZoomEnd = () => { + document.body.style.cursor = 'default'; + console.log('Zoom action ended.'); + }; + const zoom = (wheelY: number, point: Point) => { if (!svgRef.current) return; @@ -85,8 +93,20 @@ export default ({ children }: PropsWithChildren) => { const handleWheel = (e: React.WheelEvent) => { const { deltaY, clientX, clientY } = e; zoom(deltaY, { x: clientX, y: clientY }); - if (deltaY > 0) document.body.style.cursor = 'zoom-out'; - else document.body.style.cursor = 'zoom-in'; + if (deltaY > 0) { + document.body.style.cursor = 'zoom-out'; + } else { + document.body.style.cursor = 'zoom-in'; + } + + if (zoomEndTimer.current) { + clearTimeout(zoomEndTimer.current); + } + + zoomEndTimer.current = setTimeout(() => { + handleZoomEnd(); + zoomEndTimer.current = null; + }, 200); }; const handleMouseDown = (e: React.MouseEvent) => { @@ -107,6 +127,14 @@ export default ({ children }: PropsWithChildren) => { document.body.style.cursor = 'default'; }; + useEffect(() => { + return () => { + if (zoomEndTimer.current) { + clearTimeout(zoomEndTimer.current); + } + }; + }, []); + return ( { const { state: { nodes }, } = useNodeContext(); + const { + state: { groups }, + } = useGroupContext(); + const { + state: { edges }, + } = useEdgeContext(); + + const BASE_URL = import.meta.env.VITE_API_URL; + const { execute } = useFetch(urls(BASE_URL, 'privateArchi', ''), { + method: 'POST', + body: { + cost: 273, + architecture: { + nodes, + groups, + edges, + }, + title: 'fucking', + }, + }); const handleConvert = () => { if (!selectedResource) return; @@ -76,6 +100,10 @@ export default () => { const openWindow = (url: string) => window.open(url, '_blank')?.focus(); + const handleSave = () => { + execute().then((res) => console.log(res)); + }; + return ( <> @@ -85,6 +113,7 @@ export default () => { +

    + +
    + +# 🚀 기술 스택 -## **기술 스택** 🛠 +### 💻 Common Languages + +

    + JavaScript + TypeScript +

    -### 📌 **Frontend** +### 🖥️ Frontend -- **React 18.3.1** -- **Vite 5.4.9** +

    + Next.js + React +

    + +### 🔧 Backend + +

    + NestJS + MySQL + Redis + Prisma + Vitest +

    -### 📌 **Backend** +### 🌐 Infrastructure + +

    + Turborepo + Docker + Docker Compose + GitHub Actions + Naver Cloud +

    + +### 🔍 DevOps & Logging + +

    + Terraform + Elasticsearch + FluentD + Kibana + Grafana + Prometheus +

    + +### 💬 Communication Tools + +

    + Slack + Zoom + Gather Town + Figma +

    -- **TypeScript 5.1.3** -- **NestJS 10.0.0** -- **Prisma 5.22.0** -- **Vitest 2.1.4** -- **MySQL** +
    --- From 701a6786bfa7a345afa74644b474cea4cc354e88 Mon Sep 17 00:00:00 2001 From: Geonhyuk Seo Date: Mon, 2 Dec 2024 18:05:10 +0900 Subject: [PATCH 076/124] =?UTF-8?q?=F0=9F=93=9D=20Docs(README.md):=20rEADM?= =?UTF-8?q?E=20=EB=AC=B8=EC=84=9C=20=EC=88=9C=EC=84=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95,=20gif=20=EC=8B=9C=EB=82=98=EB=A6=AC=EC=98=A4=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 60 +++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 54 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 990006a9..29f1fa27 100644 --- a/README.md +++ b/README.md @@ -60,9 +60,55 @@ Cloud Canvas는 클라우드 인프라 설계를 **그래픽 인터페이스**

    +## 기능 시연 + +### GUI를 통한 인프라 설계 + +시나리오 + +1. 허브 페이지에서 헤더에 있는 새 캔버스 버튼을 눌러 캔버스 페이지로 이동한다. +2. 간단한 인프라를 설계한다. + +### 테라폼 코드 변환 + +시나리오 + +1. 캔버스 페이지에 완성된 인프라 아키텍처가 존재한다. +2. 캔버스 페이지 우상단에 있는 convertor 버튼을 누르면 현재 설계된 인프라를 바탕으로 변환된 테라폼 코드가 나온다. +3. 테라폼 코드를 통해 배포된 인프라를 확인한다. + +### 인프라 아키텍처 허브 업로드(프라이빗) + +시나리오 + +1. 인프라 아키텍처를 완성했다고 가정하고 캔버스 페이지에서 저장 버튼을 누른다.(/canvas) +2. 저장이 완료되면, 새로고침 되며 발급받은 프라이빗 아키텍처를 parameter 붙여 private architecture 캔버스 페이지로 이동한다.(/canvas/private-architecutes/{id}) +3. 해당 아키텍처가 캔버스에 다시 불러와진 것을 확인하면 허브 페이지로 이동한다.(/로 이동) +4. 허브 페이지에서 마이페이지로 이동하고 프라이빗 아키텍처 목록에서 새로운 목록이 추가된 것을 확인하고 클릭한다. +5. 새로 추가된 프라이빗 아키텍처 목록을 클릭하면 다시 캔버스 페이지로 이동한다. + +### 인프라 아키텍처 허브 업로드(퍼블릭) + +시나리오 + +1. 인프라 아키텍처를 완성했다고 가정하고 캔버스 페이지에서 저장 버튼을 누른다.(/canvas) +2. 저장이 완료되면, 새로고침 되며 발급받은 프라이빗 아키텍처를 parameter 붙여 private architecture 캔버스 페이지로 이동한다.(/canvas/private-architecutes/{id}) +3. 해당 아키텍처가 캔버스에 다시 불러와진 것을 확인하면 허브 페이지로 이동한다.(/로 이동) +4. 허브 페이지에서 마이페이지로 이동하고 프라이빗 아키텍처 목록에서 새로운 목록이 추가된 것을 확인하고 클릭한다. +5. 새로 추가된 프라이빗 아키텍처 목록을 클릭하면 다시 캔버스 페이지로 이동한다. + +### 인프라 아키텍처 허브 임포트 + +시나리오 + +1. 허브 페이지에서 아무 인프라 아키텍처 목록을 클릭한다. +2. 퍼블릭 인프라 아키텍처 상세 페이지로 이동하고 임포트 버튼을 클릭한다. +3. 임포트가 완료되면 캔버스 페이지로 리다이렉트 하고 임포트 되어 해당 아키텍처가 캔버스로 그려진 것을 확인한다. +4. 마이페이지로 들어가서, 임포트한 퍼블릭 인프라 아키텍처가 임포트 목록에 추가된 것을 확인한다. +
    -# 🚀 기술 스택 +## 🚀 기술 스택 ### 💻 Common Languages @@ -132,6 +178,13 @@ Cloud Canvas는 클라우드 인프라 설계를 **그래픽 인터페이스** cicd +## 우리의 Next! + +## 🌈 **함께하세요!** + +> **Cloud Canvas로 클라우드 설계의 새로운 가능성을 경험해보세요!** +> 프로젝트의 진행 상황과 더 많은 정보를 원하신다면 [GitHub Wiki](https://github.com/boostcampwm-2024/web37-cloud-canvas/wiki)에서 확인하세요. 😊 + ## **팀 소개** 👩‍💻 > 다양한 배경과 경험을 가진 네 명의 팀원이 Cloud Canvas를 만들고 있습니다. @@ -144,8 +197,3 @@ Cloud Canvas는 클라우드 인프라 설계를 **그래픽 인터페이스** | 커피 | 빵 | 고기 | 국수 | --- - -## 🌈 **함께하세요!** - -> **Cloud Canvas로 클라우드 설계의 새로운 가능성을 경험해보세요!** -> 프로젝트의 진행 상황과 더 많은 정보를 원하신다면 [GitHub Wiki](https://github.com/boostcampwm-2024/web37-cloud-canvas/wiki)에서 확인하세요. 😊 From 590eb240ff0fd52aa4c9c34f5f02ef33286de959 Mon Sep 17 00:00:00 2001 From: p1n9d3v Date: Mon, 2 Dec 2024 20:18:18 +0900 Subject: [PATCH 077/124] =?UTF-8?q?=F0=9F=A4=96=20Refactor(client):=20head?= =?UTF-8?q?er,=20api=EC=9A=94=EC=B2=AD=20=EB=A6=AC=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/client/package.json | 1 + apps/client/src/App.tsx | 81 +--------- apps/client/src/CloudGraph.tsx | 6 +- apps/client/src/Root.tsx | 15 ++ apps/client/src/apis/index.ts | 7 +- apps/client/src/apis/loaders.ts | 36 +++++ .../src/components/CloudGraphProvider.tsx | 37 +++++ .../Layout/Header/ActionsButtons.tsx | 100 ++++++++++++ .../Layout/Header/UtilityButtons.tsx | 44 +++++ .../src/components/Layout/Header/index.tsx | 153 +----------------- apps/client/src/components/Layout/index.tsx | 35 ++++ apps/client/src/main.tsx | 29 +++- apps/client/src/models/ncloud/MySQLDB.ts | 2 +- 13 files changed, 307 insertions(+), 239 deletions(-) create mode 100644 apps/client/src/Root.tsx create mode 100644 apps/client/src/apis/loaders.ts create mode 100644 apps/client/src/components/CloudGraphProvider.tsx create mode 100644 apps/client/src/components/Layout/Header/ActionsButtons.tsx create mode 100644 apps/client/src/components/Layout/Header/UtilityButtons.tsx create mode 100644 apps/client/src/components/Layout/index.tsx diff --git a/apps/client/package.json b/apps/client/package.json index 185f7f83..00dbbdd6 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -20,6 +20,7 @@ "nanoid": "^5.0.8", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-router-dom": "^7.0.1", "react-syntax-highlighter": "^15.6.1", "react-type-animation": "^3.2.0", "terraform": "file:../../packages/terraform" diff --git a/apps/client/src/App.tsx b/apps/client/src/App.tsx index be29247a..a95cc57b 100644 --- a/apps/client/src/App.tsx +++ b/apps/client/src/App.tsx @@ -1,90 +1,11 @@ import CloudGraph from '@/src/CloudGraph'; -import Header from '@components/Layout/Header'; -import Sidebar from '@components/Layout/Sidebar'; import NetworksBar from '@components/NCloud/NetworksBar/index'; import PropertiesBar from '@components/NCloud/PropertiesBar'; -import { DimensionProvider } from '@contexts/DimensionContext'; -import { EdgeProvider } from '@contexts/EdgeContext'; -import { GraphProvider } from '@contexts/GraphConetxt'; -import { GroupProvider } from '@contexts/GroupContext'; -import { NodeProvider } from '@contexts/NodeContext'; -import { SelectionProvider } from '@contexts/SelectionContext'; -import { SvgProvider } from '@contexts/SvgContext'; -import useFetch from '@hooks/useFetch'; -import Box from '@mui/material/Box'; -import { ReactNode, useLayoutEffect } from 'react'; -import { urls } from './apis'; - -const BASE_URL = import.meta.env.VITE_API_URL; - -export const CloudGraphProvider = ({ children }: { children: ReactNode }) => { - const { - error: errorLogin, - loading: loadingLogin, - execute: postLogin, - } = useFetch(urls(BASE_URL, 'login'), { - method: 'POST', - }); - const { - error: errorArchi, - loading: loadingArchi, - data, - execute, - } = useFetch(urls(BASE_URL, 'privateArchi', 1), { - method: 'GET', - }); - - useLayoutEffect(() => { - postLogin({}); - }, []); - - if (loadingArchi || loadingLogin) return null; - if (errorArchi || errorLogin) return
    error
    ; - - return ( - - - - - - - - {children} - - - - - - - - ); -}; export const App = () => { return ( <> - - - -
    - - - - + diff --git a/apps/client/src/CloudGraph.tsx b/apps/client/src/CloudGraph.tsx index 21f472e1..7789cdb1 100644 --- a/apps/client/src/CloudGraph.tsx +++ b/apps/client/src/CloudGraph.tsx @@ -6,6 +6,7 @@ import Graph from '@components/Graph'; import GridBackground from '@components/GridBackground'; import Group from '@components/Group'; import Node from '@components/Node'; +import NodeActions from '@components/NodeActions'; import { useEdgeContext } from '@contexts/EdgeContext'; import { useGraphContext } from '@contexts/GraphConetxt'; import { useGroupContext } from '@contexts/GroupContext'; @@ -13,10 +14,7 @@ import { useNodeContext } from '@contexts/NodeContext'; import useConnection from '@hooks/useConnection'; import useGraph from '@hooks/useGraph'; import useSelection from '@hooks/useSelection'; -import { Box } from '@mui/material'; -import { useEffect, useLayoutEffect, useMemo, useRef } from 'react'; -import SpeedDial from '@components/SpeedDial'; -import NodeActions from '@components/NodeActions'; +import { useEffect, useLayoutEffect, useRef } from 'react'; export default () => { const { diff --git a/apps/client/src/Root.tsx b/apps/client/src/Root.tsx new file mode 100644 index 00000000..f7ef7377 --- /dev/null +++ b/apps/client/src/Root.tsx @@ -0,0 +1,15 @@ +import { useLoaderData } from 'react-router-dom'; +import CloudGraphProvider from '@components/CloudGraphProvider'; +import Layout from '@components/Layout'; + +function Root() { + const loader = useLoaderData(); + + return ( + + + + ); +} + +export default Root; diff --git a/apps/client/src/apis/index.ts b/apps/client/src/apis/index.ts index 2e73c716..0f26c1cb 100644 --- a/apps/client/src/apis/index.ts +++ b/apps/client/src/apis/index.ts @@ -1,12 +1,13 @@ +const BASE_URL = import.meta.env.VITE_API_URL; export const URLS = { login: 'auth/login', privateArchi: (id: string) => `private-architectures/${id}`, }; -export const urls = (domain: string, path: keyof typeof URLS, slug?: any) => { +export const urls = (path: keyof typeof URLS, slug?: any) => { const urls = URLS[path]; if (typeof urls === 'function') { - return `${domain}/${urls(slug)}`; + return `${BASE_URL}/${urls(slug)}`; } - return `${domain}/${urls}`; + return `${BASE_URL}/${urls}`; }; diff --git a/apps/client/src/apis/loaders.ts b/apps/client/src/apis/loaders.ts new file mode 100644 index 00000000..221f5310 --- /dev/null +++ b/apps/client/src/apis/loaders.ts @@ -0,0 +1,36 @@ +import { LoaderFunctionArgs } from 'react-router-dom'; +import { urls } from '.'; + +export const rootLoader = async ({ params }: LoaderFunctionArgs) => { + const loginResponse = await fetch(urls('login'), { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({}), + credentials: 'include', + }); + + if (!loginResponse.ok) { + throw new Response('Login failed', { status: loginResponse.status }); + } + + if (!params.id) return null; + const archiResponse = await fetch(urls('privateArchi', params.id), { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'include', + }); + + if (!archiResponse.ok) { + throw new Response('Data fetch failed', { + status: archiResponse.status, + }); + } + + const data = await archiResponse.json(); + + return { data }; +}; diff --git a/apps/client/src/components/CloudGraphProvider.tsx b/apps/client/src/components/CloudGraphProvider.tsx new file mode 100644 index 00000000..d0495557 --- /dev/null +++ b/apps/client/src/components/CloudGraphProvider.tsx @@ -0,0 +1,37 @@ +import { DimensionProvider } from '@contexts/DimensionContext'; +import { EdgeProvider } from '@contexts/EdgeContext'; +import { GraphProvider } from '@contexts/GraphConetxt'; +import { GroupProvider } from '@contexts/GroupContext'; +import { NodeProvider } from '@contexts/NodeContext'; +import { SelectionProvider } from '@contexts/SelectionContext'; +import { SvgProvider } from '@contexts/SvgContext'; +import { Edge, Group, Node } from '@types'; +import { ReactNode } from 'react'; + +type Props = { + children: ReactNode; + initialData: { + nodes: Record; + edges: Record; + groups: Record; + }; +}; +export default ({ children, initialData }: Props) => { + return ( + + + + + + + + {children} + + + + + + + + ); +}; diff --git a/apps/client/src/components/Layout/Header/ActionsButtons.tsx b/apps/client/src/components/Layout/Header/ActionsButtons.tsx new file mode 100644 index 00000000..96bfe38c --- /dev/null +++ b/apps/client/src/components/Layout/Header/ActionsButtons.tsx @@ -0,0 +1,100 @@ +import { urls } from '@/src/apis'; +import { ServerRequiredFields } from '@/src/models/ncloud/Server'; +import { transformObject, validateObject } from '@/src/models/ncloud/utils'; +import CodeDrawer from '@components/CodeDrawer'; +import { useDimensionContext } from '@contexts/DimensionContext'; +import { useEdgeContext } from '@contexts/EdgeContext'; +import { useGroupContext } from '@contexts/GroupContext'; +import { useNodeContext } from '@contexts/NodeContext'; +import useFetch from '@hooks/useFetch'; +import useNCloud from '@hooks/useNCloud'; +import { Button, ToggleButton, ToggleButtonGroup } from '@mui/material'; +import { useState } from 'react'; +import { useLocation, useNavigate } from 'react-router-dom'; +import { TerraformConverter } from 'terraform/converter/TerraformConverter'; + +export default () => { + const { + state: { nodes }, + } = useNodeContext(); + const { + state: { groups }, + } = useGroupContext(); + const { + state: { edges }, + } = useEdgeContext(); + + const { selectedResource } = useNCloud(); + const { dimension, changeDimension } = useDimensionContext(); + const [openDrawer, setOpenDrawer] = useState(false); + const [terraformCode, setTerraformCode] = useState(''); + const navigate = useNavigate(); + const url = useLocation(); + + const { execute } = useFetch(urls('privateArchi', ''), { + method: 'POST', + body: { + cost: 273, + architecture: { + nodes, + groups, + edges, + }, + title: 'fucking', + }, + }); + + const handleConvertTerraform = () => { + if (!selectedResource) return; + const nodeProperties = { + type: selectedResource.type, + name: selectedResource.properties.name, + properties: transformObject(selectedResource.properties), + }; + + if (!validateObject(nodeProperties.properties, ServerRequiredFields)) { + alert('Is not valid'); + return; + } + + const Converter = new TerraformConverter(); + Converter.addResourceFromJson([nodeProperties]); + setTerraformCode(Converter.generate()); + setOpenDrawer(true); + }; + + const handleSave = () => { + execute().then((res) => console.log(res)); + }; + + return ( + <> + + + + changeDimension(dimension === '2d' ? '3d' : '2d') + } + sx={{ + height: '38px', + }} + > + 2D + 3D + + + setOpenDrawer(false)} + /> + + ); +}; diff --git a/apps/client/src/components/Layout/Header/UtilityButtons.tsx b/apps/client/src/components/Layout/Header/UtilityButtons.tsx new file mode 100644 index 00000000..d882225b --- /dev/null +++ b/apps/client/src/components/Layout/Header/UtilityButtons.tsx @@ -0,0 +1,44 @@ +import { styled, useColorScheme } from '@mui/material/styles'; +import { IconButton, ButtonGroup, Divider } from '@mui/material'; +import DarkModeIcon from '@mui/icons-material/DarkMode'; +import GitHubIcon from '@mui/icons-material/GitHub'; +import LightModeIcon from '@mui/icons-material/LightMode'; +import ManageHistoryIcon from '@mui/icons-material/ManageHistory'; + +const GITHUB_URL = 'https://github.com/boostcampwm-2024/web37-cloud-canvas'; +const NOTION_URL = + 'https://pleasant-muenster-8f5.notion.site/Boostcamp-Web37-cloud-canvas-12a389341f0a806dbb98d597fd7b4e52?pvs=4'; + +const StyledIconButton = styled(IconButton)(({ theme }) => ({ + borderRadius: '12px', + border: `1px solid ${theme.palette.divider}`, + scale: 0.8, + [`&:hover`]: { + border: `1px solid ${theme.palette.primary.main}`, + color: theme.palette.primary.main, + transition: 'border 0.3s linear', + }, +})); + +export default () => { + const { mode: themeMode, setMode: setThemeMode } = useColorScheme(); + const handleToggleTheme = () => + setThemeMode(themeMode === 'dark' ? 'light' : 'dark'); + + const openWindow = (url: string) => window.open(url, '_blank')?.focus(); + return ( + + openWindow(GITHUB_URL)}> + + + + openWindow(NOTION_URL)}> + + + + + {themeMode === 'dark' ? : } + + + ); +}; diff --git a/apps/client/src/components/Layout/Header/index.tsx b/apps/client/src/components/Layout/Header/index.tsx index 31802bbd..121caf22 100644 --- a/apps/client/src/components/Layout/Header/index.tsx +++ b/apps/client/src/components/Layout/Header/index.tsx @@ -1,27 +1,10 @@ -import { urls } from '@/src/apis'; -import { ServerRequiredFields } from '@/src/models/ncloud/Server'; -import { transformObject, validateObject } from '@/src/models/ncloud/utils'; -import CodeDrawer from '@components/CodeDrawer'; -import { useDimensionContext } from '@contexts/DimensionContext'; -import { useEdgeContext } from '@contexts/EdgeContext'; -import { useGroupContext } from '@contexts/GroupContext'; -import { useNodeContext } from '@contexts/NodeContext'; -import useFetch from '@hooks/useFetch'; -import useNCloud from '@hooks/useNCloud'; -import DarkModeIcon from '@mui/icons-material/DarkMode'; -import GitHubIcon from '@mui/icons-material/GitHub'; -import LightModeIcon from '@mui/icons-material/LightMode'; -import ManageHistoryIcon from '@mui/icons-material/ManageHistory'; -import { Button, ToggleButton, ToggleButtonGroup } from '@mui/material'; import Box from '@mui/material/Box'; -import ButtonGroup from '@mui/material/ButtonGroup'; -import Divider from '@mui/material/Divider'; -import IconButton from '@mui/material/IconButton'; import Stack from '@mui/material/Stack'; -import { styled, useColorScheme } from '@mui/material/styles'; +import { styled } from '@mui/material/styles'; import Typography from '@mui/material/Typography'; -import { TerraformConverter } from 'node_modules/terraform/converter/TerraformConverter'; -import { useState } from 'react'; +import { useLocation, useNavigate } from 'react-router-dom'; +import ActionsButtons from './ActionsButtons'; +import UtilityButtons from './UtilityButtons'; const StyledBox = styled(Box)(({ theme }) => ({ display: 'flex', @@ -31,79 +14,7 @@ const StyledBox = styled(Box)(({ theme }) => ({ padding: theme.spacing(1, 2), })); -const GITHUB_URL = 'https://github.com/boostcampwm-2024/web37-cloud-canvas'; -const NOTION_URL = - 'https://pleasant-muenster-8f5.notion.site/Boostcamp-Web37-cloud-canvas-12a389341f0a806dbb98d597fd7b4e52?pvs=4'; - -const StyledIconButton = styled(IconButton)(({ theme }) => ({ - borderRadius: '12px', - border: `1px solid ${theme.palette.divider}`, - scale: 0.8, - [`&:hover`]: { - border: `1px solid ${theme.palette.primary.main}`, - color: theme.palette.primary.main, - transition: 'border 0.3s linear', - }, -})); - export default () => { - const { mode: themeMode, setMode: setThemeMode } = useColorScheme(); - const { dimension, changeDimension } = useDimensionContext(); - const [openDrawer, setOpenDrawer] = useState(false); - const [terraformCode, setTerraformCode] = useState(''); - const { selectedResource } = useNCloud(); - const { - state: { nodes }, - } = useNodeContext(); - const { - state: { groups }, - } = useGroupContext(); - const { - state: { edges }, - } = useEdgeContext(); - - const BASE_URL = import.meta.env.VITE_API_URL; - const { execute } = useFetch(urls(BASE_URL, 'privateArchi', ''), { - method: 'POST', - body: { - cost: 273, - architecture: { - nodes, - groups, - edges, - }, - title: 'fucking', - }, - }); - - const handleConvert = () => { - if (!selectedResource) return; - const nodeProperties = { - type: selectedResource.type, - name: selectedResource.properties.name, - properties: transformObject(selectedResource.properties), - }; - - if (!validateObject(nodeProperties.properties, ServerRequiredFields)) { - alert('Is not valid'); - return; - } - - const Converter = new TerraformConverter(); - Converter.addResourceFromJson([nodeProperties]); - setTerraformCode(Converter.generate()); - setOpenDrawer(true); - }; - - const handleToggleTheme = () => - setThemeMode(themeMode === 'dark' ? 'light' : 'dark'); - - const openWindow = (url: string) => window.open(url, '_blank')?.focus(); - - const handleSave = () => { - execute().then((res) => console.log(res)); - }; - return ( <> @@ -113,62 +24,10 @@ export default () => { - - - - changeDimension(dimension === '2d' ? '3d' : '2d') - } - sx={{ - height: '38px', - }} - > - 2D - 3D - - - openWindow(GITHUB_URL)} - > - - - - openWindow(NOTION_URL)} - > - - - - - {themeMode === 'dark' ? ( - - ) : ( - - )} - - + + - setOpenDrawer(false)} - /> ); }; diff --git a/apps/client/src/components/Layout/index.tsx b/apps/client/src/components/Layout/index.tsx new file mode 100644 index 00000000..3f95cf9f --- /dev/null +++ b/apps/client/src/components/Layout/index.tsx @@ -0,0 +1,35 @@ +import NetworksBar from '@components/NCloud/NetworksBar'; +import PropertiesBar from '@components/NCloud/PropertiesBar'; +import { Box } from '@mui/material'; +import { Outlet } from 'react-router-dom'; +import Header from './Header'; +import Sidebar from './Sidebar'; + +export default () => { + return ( + <> + + + +
    + + + + + + + + ); +}; diff --git a/apps/client/src/main.tsx b/apps/client/src/main.tsx index 95b8be7a..bffa8646 100644 --- a/apps/client/src/main.tsx +++ b/apps/client/src/main.tsx @@ -2,15 +2,36 @@ import { CssBaseline, ThemeProvider } from '@mui/material'; import theme from '@theme'; import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; -import { App, CloudGraphProvider } from './App.tsx'; +import { createBrowserRouter, RouterProvider } from 'react-router-dom'; +import { App } from './App.tsx'; +import Root from './Root.tsx'; +import { rootLoader } from './apis/loaders.ts'; +const router = createBrowserRouter([ + { + element: , + loader: rootLoader, + children: [ + { + path: '/', + element: , + }, + { + path: ':id', + element: , + }, + { + path: '*', + element:
    Not Found
    , + }, + ], + }, +]); createRoot(document.getElementById('root')!).render( - - - + , ); diff --git a/apps/client/src/models/ncloud/MySQLDB.ts b/apps/client/src/models/ncloud/MySQLDB.ts index 364b49e9..fe742c66 100644 --- a/apps/client/src/models/ncloud/MySQLDB.ts +++ b/apps/client/src/models/ncloud/MySQLDB.ts @@ -11,7 +11,7 @@ export const MySQLDBNode: Node = { type: 'db-mysql', size: { '2d': { width: 90, height: 90 }, - '3d': { width: 128, height: 137.5, depth: 82, offset: 0 }, + '3d': { width: 128, height: 137.5, offset: 0 }, }, properties: { ...Networks, From 732e8d0dc63472a7af53aeb1c428a02d57d05efb Mon Sep 17 00:00:00 2001 From: Geonhyuk Seo Date: Mon, 2 Dec 2024 20:22:01 +0900 Subject: [PATCH 078/124] =?UTF-8?q?=F0=9F=93=9D=20Docs(README.md):=20?= =?UTF-8?q?=ED=94=84=EB=A1=9C=EC=A0=9D=ED=8A=B8=20=EC=95=84=EC=9D=B4?= =?UTF-8?q?=EC=BD=98=20=EC=97=85=EB=A1=9C=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 29f1fa27..7c9e7af2 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

    - Cloud Canvas +cicd

    🎨 Cloud Canvas 🎨

    From c9cde2641d50c7c2eb95086f6cee500f2853df37 Mon Sep 17 00:00:00 2001 From: p1n9d3v Date: Mon, 2 Dec 2024 21:49:07 +0900 Subject: [PATCH 079/124] =?UTF-8?q?=F0=9F=A4=96=20Refactor(client):=20dime?= =?UTF-8?q?nsion=20=EB=B3=80=EA=B2=BD=EC=8B=9C=20=EC=9D=BC=EC=A0=95=20?= =?UTF-8?q?=EC=9C=84=EC=B9=98=20=EC=A7=80=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/contexts/GraphConetxt/index.tsx | 8 ++- apps/client/src/helpers/node.ts | 23 +++++++ apps/client/src/hooks/useGraph.ts | 63 +++++-------------- 3 files changed, 43 insertions(+), 51 deletions(-) diff --git a/apps/client/src/contexts/GraphConetxt/index.tsx b/apps/client/src/contexts/GraphConetxt/index.tsx index ce705a13..8c0579e5 100644 --- a/apps/client/src/contexts/GraphConetxt/index.tsx +++ b/apps/client/src/contexts/GraphConetxt/index.tsx @@ -41,8 +41,12 @@ export const GraphProvider = ({ payload: { x: state.viewBox.x || 0, y: state.viewBox.y || 0, - width: (state.viewBox.width || svg.clientWidth) * 2, - height: (state.viewBox.height || svg.clientHeight) * 2, + width: + (state.viewBox.width || svg.clientWidth) * + initialZoomFactor, + height: + (state.viewBox.height || svg.clientHeight) * + initialZoomFactor, }, }); }; diff --git a/apps/client/src/helpers/node.ts b/apps/client/src/helpers/node.ts index f39c5411..c27a5d4b 100644 --- a/apps/client/src/helpers/node.ts +++ b/apps/client/src/helpers/node.ts @@ -83,3 +83,26 @@ export const alignNodePoint = ( return result; }; + +export const calculateNodeBoundingBox = ( + nodes: Record, + dimension: Dimension, +) => { + const nodesArr = Object.values(nodes); + if (nodesArr.length === 0) return null; + + const minX = Math.min(...nodesArr.map((node) => node.point.x)); + const minY = Math.min(...nodesArr.map((node) => node.point.y)); + + const maxX = Math.max( + ...nodesArr.map((node) => node.point.x + node.size[dimension].width), + ); + const maxY = Math.max( + ...nodesArr.map((node) => node.point.y + node.size[dimension].height), + ); + + const width = maxX - minX; + const height = maxY - minY; + + return { minX, minY, width, height }; +}; diff --git a/apps/client/src/hooks/useGraph.ts b/apps/client/src/hooks/useGraph.ts index 546b28f6..97eac364 100644 --- a/apps/client/src/hooks/useGraph.ts +++ b/apps/client/src/hooks/useGraph.ts @@ -10,7 +10,12 @@ import { updateNearestConnectorPair, } from '@helpers/edge'; import { computeBounds } from '@helpers/group'; -import { adjustNodePointForDimension, alignNodePoint } from '@helpers/node'; +import { + adjustNodePointForDimension, + alignNodePoint, + calculateNodeBoundingBox, +} from '@helpers/node'; +import { calculateViewBox } from '@helpers/viewbox'; import useSelection from '@hooks/useSelection'; import { Connection, Dimension, Edge, Group, Node, Point } from '@types'; import { @@ -165,60 +170,20 @@ export default () => { }; }, {}); - // const updatedEdgePairs = Object.entries(updatedNodes).reduce( - // (acc, [id, node]) => { - // const connectedEdges = Object.values(updatedEdges).filter( - // (edge) => edge.source.id === id || edge.target.id === id, - // ); - // const result = updateNearestConnectorPair( - // node, - // updatedNodes, - // connectedEdges as Edge[], - // ); - // return { - // ...acc, - // ...result, - // }; - // }, - // {}, - // ); - //INFO: update ViewBox - if (Object.keys(updatedNodes).length === 0) return; - const updatedNodesArr = Object.values(updatedNodes); - const minX = Math.min( - ...updatedNodesArr.map((node: Node) => node.point.x), - ); - const minY = Math.min( - ...updatedNodesArr.map((node: Node) => node.point.y), - ); - - const maxX = Math.max( - ...updatedNodesArr.map( - (node: Node) => node.point.x + node.size[dimension].width, - ), - ); - const maxY = Math.max( - ...updatedNodesArr.map( - (node: Node) => node.point.y + node.size[dimension].height, - ), - ); - - const nodesWidth = maxX - minX; - const nodesHeight = maxY - minY; - - const centerX = minX + nodesWidth / 2; - const centerY = minY + nodesHeight / 2; + if (Object.keys(updatedNodes).length === 0 || !svgRef.current) return; + const allNodeBounds = calculateNodeBoundingBox(updatedNodes, dimension); - const paddingWidth = (viewBox.x + viewBox.width) / 2; - const paddingHeight = (viewBox.y + viewBox.height) / 2; + const paddingWidth = svgRef.current.clientWidth ?? 0; + const paddingHeight = svgRef.current?.clientHeight ?? 0; graphDispatch({ type: 'SET_VIEWBOX', payload: { - ...viewBox, - x: centerX - paddingWidth, - y: centerY - paddingHeight, + x: (allNodeBounds?.minX || 0) - paddingWidth, + y: (allNodeBounds?.minY || 0) - paddingHeight, + width: viewBox.width, + height: viewBox.height, }, }); From e882c8d0bc3594bcc83e0da11b09860f74a46753 Mon Sep 17 00:00:00 2001 From: p1n9d3v Date: Mon, 2 Dec 2024 22:35:35 +0900 Subject: [PATCH 080/124] =?UTF-8?q?=F0=9F=A4=96=20Refactor(client):=20?= =?UTF-8?q?=EB=85=B8=EB=93=9C=20=EB=93=9C=EB=9E=98=EA=B7=B8=20=EB=B0=8F=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=EC=8B=9C=20=EC=9C=84=EC=B9=98=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/CloudGraphProvider.tsx | 2 +- apps/client/src/components/Node/index.tsx | 9 +++++-- apps/client/src/helpers/cloud.ts | 15 ----------- apps/client/src/helpers/node.ts | 1 - apps/client/src/hooks/useGraph.ts | 27 +++++++++---------- apps/client/src/hooks/useNCloud.ts | 14 ++++++++-- 6 files changed, 33 insertions(+), 35 deletions(-) delete mode 100644 apps/client/src/helpers/cloud.ts diff --git a/apps/client/src/components/CloudGraphProvider.tsx b/apps/client/src/components/CloudGraphProvider.tsx index d0495557..c8e7af0c 100644 --- a/apps/client/src/components/CloudGraphProvider.tsx +++ b/apps/client/src/components/CloudGraphProvider.tsx @@ -20,7 +20,7 @@ export default ({ children, initialData }: Props) => { return ( - + diff --git a/apps/client/src/components/Node/index.tsx b/apps/client/src/components/Node/index.tsx index d2b78e9c..24bc4cb6 100644 --- a/apps/client/src/components/Node/index.tsx +++ b/apps/client/src/components/Node/index.tsx @@ -8,6 +8,7 @@ import { Node, Point } from '@types'; import { useEffect } from 'react'; import LoadBalancerNode from './ncloud/LoadBalancer'; import NatGatewayNode from './ncloud/NatGateway'; +import useGraph from '@hooks/useGraph'; const nodeFactory = (node: Node) => { switch (node.type) { @@ -39,6 +40,7 @@ type Props = { export default ({ node, isSelected, onMove, onSelect, onRemove }: Props) => { const { id, point } = node; + const { svgRef } = useGraph(); const { isDragging, startDrag, drag, stopDrag } = useDrag({ initialPoint: point, updateFn: (newPoint) => onMove(id, newPoint), @@ -68,14 +70,17 @@ export default ({ node, isSelected, onMove, onSelect, onRemove }: Props) => { }; useEffect(() => { + if (!svgRef.current) return; if (isDragging) { document.addEventListener('mousemove', handleMouseMove); document.addEventListener('mouseup', handleMouseUp); + svgRef.current.addEventListener('mouseleave', handleMouseUp); } return () => { - document.removeEventListener('mousemove', handleMouseMove); - document.removeEventListener('mouseup', handleMouseUp); + document?.removeEventListener('mousemove', handleMouseMove); + document?.removeEventListener('mouseup', handleMouseUp); + svgRef.current?.removeEventListener('mouseleave', handleMouseUp); }; }, [isDragging]); diff --git a/apps/client/src/helpers/cloud.ts b/apps/client/src/helpers/cloud.ts deleted file mode 100644 index d5409788..00000000 --- a/apps/client/src/helpers/cloud.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { getSvgPoint } from '@utils'; - -export const getInitPoint = (svg: SVGSVGElement) => { - const svgRect = svg.getBoundingClientRect(); - - const leftCenterSvg = getSvgPoint(svg, { - x: svgRect.left, - y: svgRect.top, - }); - - return { - x: leftCenterSvg.x + 200, - y: svgRect.height / 3, - }; -}; diff --git a/apps/client/src/helpers/node.ts b/apps/client/src/helpers/node.ts index c27a5d4b..ef28980a 100644 --- a/apps/client/src/helpers/node.ts +++ b/apps/client/src/helpers/node.ts @@ -89,7 +89,6 @@ export const calculateNodeBoundingBox = ( dimension: Dimension, ) => { const nodesArr = Object.values(nodes); - if (nodesArr.length === 0) return null; const minX = Math.min(...nodesArr.map((node) => node.point.x)); const minY = Math.min(...nodesArr.map((node) => node.point.y)); diff --git a/apps/client/src/hooks/useGraph.ts b/apps/client/src/hooks/useGraph.ts index 97eac364..c0e2ac56 100644 --- a/apps/client/src/hooks/useGraph.ts +++ b/apps/client/src/hooks/useGraph.ts @@ -172,20 +172,19 @@ export default () => { //INFO: update ViewBox if (Object.keys(updatedNodes).length === 0 || !svgRef.current) return; - const allNodeBounds = calculateNodeBoundingBox(updatedNodes, dimension); - - const paddingWidth = svgRef.current.clientWidth ?? 0; - const paddingHeight = svgRef.current?.clientHeight ?? 0; - - graphDispatch({ - type: 'SET_VIEWBOX', - payload: { - x: (allNodeBounds?.minX || 0) - paddingWidth, - y: (allNodeBounds?.minY || 0) - paddingHeight, - width: viewBox.width, - height: viewBox.height, - }, - }); + // const allNodeBounds = calculateNodeBoundingBox(updatedNodes, dimension); + // + // const paddingWidth = svgRef.current.clientWidth ?? 0; + // const paddingHeight = svgRef.current?.clientHeight ?? 0; + // + // graphDispatch({ + // type: 'SET_VIEWBOX', + // payload: { + // ...viewBox, + // width: viewBox.width, + // height: viewBox.height, + // }, + // }); nodeDispatch({ type: 'UPDATE_NODES', diff --git a/apps/client/src/hooks/useNCloud.ts b/apps/client/src/hooks/useNCloud.ts index 6a5f0d35..fe87cf17 100644 --- a/apps/client/src/hooks/useNCloud.ts +++ b/apps/client/src/hooks/useNCloud.ts @@ -1,6 +1,7 @@ import { NcloudGroupFactory, NcloudNodeFactory } from '@/src/models/ncloud'; import { DEFAULT_REGION, REGIONS } from '@/src/models/ncloud/constants'; -import { getInitPoint } from '@helpers/cloud'; +import { useDimensionContext } from '@contexts/DimensionContext'; +import { useGraphContext } from '@contexts/GraphConetxt'; import useGraph from '@hooks/useGraph'; import useSelection from '@hooks/useSelection'; import { Region } from '@types'; @@ -36,6 +37,11 @@ export default () => { isExistGroup, } = useGraph(); + const { + state: { viewBox }, + } = useGraphContext(); + const { dimension } = useDimensionContext(); + useEffect(() => { if (!selectedNodeId || !nodes[selectedNodeId]) { setSelectedResource(undefined); @@ -80,6 +86,7 @@ export default () => { const createResource = (type: string) => { if (!svgRef.current) return; + console.log(viewBox); const node = NcloudNodeFactory(type); const id = `node-${nanoid()}`; @@ -94,7 +101,10 @@ export default () => { value: region.value, }, }, - point: getInitPoint(svgRef.current!), + point: { + x: viewBox.x + 300, + y: viewBox.y + viewBox.height / 2, + }, }); const regionId = REGIONS[DEFAULT_REGION].id; From 91c142090669a430391ec77c8e295c9cb03dc692 Mon Sep 17 00:00:00 2001 From: Geonhyuk Seo Date: Mon, 2 Dec 2024 22:50:08 +0900 Subject: [PATCH 081/124] =?UTF-8?q?=F0=9F=93=9D=20Docs(README.md):=20?= =?UTF-8?q?=EC=9D=B8=ED=94=84=EB=9D=BC=20=EC=95=84=ED=82=A4=ED=85=8D?= =?UTF-8?q?=EC=B2=98=20=EC=84=A4=EA=B3=84=EB=90=9C=20=EA=B1=B0=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=EC=A7=80=20=EC=97=85=EB=A1=9C=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7c9e7af2..caee7861 100644 --- a/README.md +++ b/README.md @@ -170,9 +170,13 @@ Cloud Canvas는 클라우드 인프라 설계를 **그래픽 인터페이스** ## **아키텍처** 🌐 -### **전반적인 인프라 설계** +### **인프라 설계** -![image](https://github.com/user-attachments/assets/5901b688-0d3d-4698-ad22-a4d4bb7aa8fd) +![image](https://github.com/user-attachments/assets/893a5dab-3705-40fb-b628-70724ea278c5) + +### **애플리케이션 설계** + +![image](https://github.com/user-attachments/assets/0b28bcd3-1cf1-41d0-9e81-59796bb8e66e) ### **CI/CD 파이프라인** From e2992023ebbb5e3c8019185619703618e9a2c008 Mon Sep 17 00:00:00 2001 From: p1n9d3v Date: Tue, 3 Dec 2024 02:24:58 +0900 Subject: [PATCH 082/124] =?UTF-8?q?=F0=9F=90=9E=20Fix(client):=202d,=203d?= =?UTF-8?q?=EB=B3=80=ED=99=98=EC=8B=9C=20=EC=9C=84=EC=B9=98=EC=A1=B0?= =?UTF-8?q?=EC=A0=95=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/client/src/components/FormDialog.tsx | 65 +++++++++++++++++++ apps/client/src/components/Graph/index.tsx | 20 +++--- .../src/components/GridBackground/index.tsx | 13 ++-- .../patterns/GridPatternMinor.tsx | 1 + .../Layout/Header/ActionsButtons.tsx | 5 +- apps/client/src/components/Node/index.tsx | 3 +- apps/client/src/components/SpeedDial.tsx | 2 +- .../src/contexts/GraphConetxt/index.tsx | 5 +- .../src/contexts/GraphConetxt/reducer.ts | 21 ++++-- apps/client/src/hooks/useGraph.ts | 49 ++++++++++---- apps/client/src/theme/index.ts | 1 + 11 files changed, 145 insertions(+), 40 deletions(-) create mode 100644 apps/client/src/components/FormDialog.tsx diff --git a/apps/client/src/components/FormDialog.tsx b/apps/client/src/components/FormDialog.tsx new file mode 100644 index 00000000..fa04229e --- /dev/null +++ b/apps/client/src/components/FormDialog.tsx @@ -0,0 +1,65 @@ +import * as React from 'react'; +import Button from '@mui/material/Button'; +import TextField from '@mui/material/TextField'; +import Dialog from '@mui/material/Dialog'; +import DialogActions from '@mui/material/DialogActions'; +import DialogContent from '@mui/material/DialogContent'; +import DialogContentText from '@mui/material/DialogContentText'; +import DialogTitle from '@mui/material/DialogTitle'; + +export default function FormDialog() { + const [open, setOpen] = React.useState(false); + + const handleClickOpen = () => { + setOpen(true); + }; + + const handleClose = () => { + setOpen(false); + }; + + return ( + <> + + ) => { + event.preventDefault(); + const formData = new FormData(event.currentTarget); + const formJson = Object.fromEntries( + (formData as any).entries(), + ); + const email = formJson.email; + console.log(email); + handleClose(); + }, + }} + > + Subscribe + + Import + + + + + + + + + ); +} diff --git a/apps/client/src/components/Graph/index.tsx b/apps/client/src/components/Graph/index.tsx index 860152e5..0f6776ce 100644 --- a/apps/client/src/components/Graph/index.tsx +++ b/apps/client/src/components/Graph/index.tsx @@ -8,22 +8,16 @@ import { PropsWithChildren, useRef, useEffect } from 'react'; export default ({ children }: PropsWithChildren) => { const { svgRef } = useSvgContext(); const { - state: { viewBox }, + state: { viewBox, initialViewBox }, dispatch, } = useGraphContext(); const isPanning = useRef(false); const startPoint = useRef({ x: 0, y: 0 }); const spaceActiveKey = useKey('space'); - const initialViewBox = { - x: 0, - y: 0, - width: svgRef.current?.clientWidth || 0, - height: svgRef.current?.clientHeight || 0, - }; - const MIN_ZOOM = 0.2; - const MAX_ZOOM = 1; + const MIN_ZOOM = 0.1; + const MAX_ZOOM = 3; const zoomEndTimer = useRef | null>(null); @@ -45,6 +39,9 @@ export default ({ children }: PropsWithChildren) => { if (newZoom < MIN_ZOOM || newZoom > MAX_ZOOM) { return; } + + const newWidth = viewBox.width * zoomFactor; + const newHeight = viewBox.height * zoomFactor; dispatch({ type: 'SET_VIEWBOX', payload: { @@ -54,8 +51,8 @@ export default ({ children }: PropsWithChildren) => { y: viewBox.y + (cursorSvgPoint.y - viewBox.y) * (1 - zoomFactor), - width: viewBox.width * zoomFactor, - height: viewBox.height * zoomFactor, + width: newWidth, + height: newHeight, }, }); }; @@ -141,6 +138,7 @@ export default ({ children }: PropsWithChildren) => { ref={svgRef} viewBox={`${viewBox?.x} ${viewBox?.y} ${viewBox?.width} ${viewBox?.height}`} width="100%" + preserveAspectRatio="xMidYMid meet" height="100%" onWheel={handleWheel} onMouseDown={handleMouseDown} diff --git a/apps/client/src/components/GridBackground/index.tsx b/apps/client/src/components/GridBackground/index.tsx index a713ce24..0b11406e 100644 --- a/apps/client/src/components/GridBackground/index.tsx +++ b/apps/client/src/components/GridBackground/index.tsx @@ -6,16 +6,19 @@ import GridPatternMinor from './patterns/GridPatternMinor'; export default () => { const { dimension } = useDimensionContext(); const { - state: { viewBox }, + state: { viewBox, initialViewBox }, } = useGraphContext(); const { x, y, width, height } = viewBox; + const adjustWidth = viewBox.width + initialViewBox.width; + const adjustHeight = viewBox.height + initialViewBox.height; + const points = [ - `${x},${y}`, - `${x + width},${y}`, - `${x + width},${y + height}`, - `${x},${y + height}`, + `${x - adjustWidth},${y - adjustHeight}`, + `${x + adjustWidth * 2},${y - adjustHeight}`, + `${x + adjustWidth * 2},${y + adjustHeight * 2}`, + `${x - adjustWidth},${y + adjustHeight * 2}`, ].join(' '); return ( diff --git a/apps/client/src/components/GridBackground/patterns/GridPatternMinor.tsx b/apps/client/src/components/GridBackground/patterns/GridPatternMinor.tsx index 81987491..0195a8cc 100644 --- a/apps/client/src/components/GridBackground/patterns/GridPatternMinor.tsx +++ b/apps/client/src/components/GridBackground/patterns/GridPatternMinor.tsx @@ -28,6 +28,7 @@ export default ({ points, dimension }: Props) => { width={width} height={height} patternUnits="userSpaceOnUse" + patternTransform={`scale(${1 / 5})`} > { const navigate = useNavigate(); const url = useLocation(); - const { execute } = useFetch(urls('privateArchi', ''), { + const { execute: saveArchitecture } = useFetch(urls('privateArchi', ''), { method: 'POST', body: { cost: 273, @@ -64,11 +64,12 @@ export default () => { }; const handleSave = () => { - execute().then((res) => console.log(res)); + saveArchitecture(); }; return ( <> + - ) => { - event.preventDefault(); - const formData = new FormData(event.currentTarget); - const formJson = Object.fromEntries( - (formData as any).entries(), - ); - const email = formJson.email; - console.log(email); - handleClose(); - }, - }} - > - Subscribe - - Import - - - - - - - - - ); -} diff --git a/apps/client/src/components/Layout/Header/ActionsButtons.tsx b/apps/client/src/components/Layout/Header/ActionsButtons.tsx index 9947a2a2..958ca7a0 100644 --- a/apps/client/src/components/Layout/Header/ActionsButtons.tsx +++ b/apps/client/src/components/Layout/Header/ActionsButtons.tsx @@ -2,6 +2,7 @@ import { urls } from '@/src/apis'; import { ServerRequiredFields } from '@/src/models/ncloud/Server'; import { transformObject, validateObject } from '@/src/models/ncloud/utils'; import CodeDrawer from '@components/CodeDrawer'; +import ShareDialog from '@components/ShareDialog'; import { useDimensionContext } from '@contexts/DimensionContext'; import { useEdgeContext } from '@contexts/EdgeContext'; import { useGroupContext } from '@contexts/GroupContext'; @@ -28,9 +29,17 @@ export default () => { const { dimension, changeDimension } = useDimensionContext(); const [openDrawer, setOpenDrawer] = useState(false); const [terraformCode, setTerraformCode] = useState(''); + const [open, setOpen] = useState(false); const navigate = useNavigate(); const url = useLocation(); + const handleOpenShareDialog = () => { + setOpen(true); + }; + const handleCloseShareDialog = () => { + setOpen(false); + }; + const { execute: saveArchitecture } = useFetch(urls('privateArchi', ''), { method: 'POST', body: { @@ -67,9 +76,10 @@ export default () => { saveArchitecture(); }; + console.log(open); return ( <> - + + + + + ); +}; From 1ca46c2b88415397c8e16e2ffff25ea31d7f3be3 Mon Sep 17 00:00:00 2001 From: p1n9d3v Date: Tue, 3 Dec 2024 13:11:38 +0900 Subject: [PATCH 084/124] =?UTF-8?q?=E2=9C=A8=20Feat(client):=20mysql,=20Ob?= =?UTF-8?q?ejct=20storage=20=EC=97=B0=EA=B2=B0=20=EB=B0=8F=20=EB=85=B8?= =?UTF-8?q?=EB=93=9C=EA=B0=80=20=EC=84=A0=ED=83=9D=EB=90=98=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EC=95=98=EC=9D=84=20=EB=95=8C=20=EC=A0=84=EC=B2=B4=20?= =?UTF-8?q?=EB=85=B8=EB=93=9C=20=ED=85=8C=EB=9D=BC=ED=8F=BC=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=ED=99=98=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/client/package.json | 4 +- .../Layout/Header/ActionsButtons.tsx | 52 +++++- .../PropertiesBar/MySQLDBProperties.tsx | 173 ++++++++++++++++++ .../PropertiesBar/ObjectStorageProperties.tsx | 53 ++++++ .../components/NCloud/PropertiesBar/index.tsx | 6 + apps/client/src/constants/index.ts | 7 - apps/client/src/models/ncloud/MySQLDB.ts | 86 ++++++++- apps/client/src/models/ncloud/Networks.ts | 7 + .../client/src/models/ncloud/ObjectStorage.ts | 10 +- apps/client/src/models/ncloud/Server.ts | 3 + apps/client/src/models/ncloud/index.ts | 22 ++- packages/terraform/util/resource.ts | 7 +- packages/terraform/util/resourceParser.ts | 30 +-- pnpm-lock.yaml | 147 ++++++++++++++- 14 files changed, 553 insertions(+), 54 deletions(-) create mode 100644 apps/client/src/components/NCloud/PropertiesBar/MySQLDBProperties.tsx create mode 100644 apps/client/src/components/NCloud/PropertiesBar/ObjectStorageProperties.tsx diff --git a/apps/client/package.json b/apps/client/package.json index b08bebd5..76da3029 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -17,6 +17,7 @@ "@emotion/styled": "^11.13.0", "@mui/icons-material": "^6.1.5", "@mui/material": "^6.1.5", + "@types/validator": "^13.12.2", "nanoid": "^5.0.8", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -24,7 +25,8 @@ "react-select": "^5.8.3", "react-syntax-highlighter": "^15.6.1", "react-type-animation": "^3.2.0", - "terraform": "file:../../packages/terraform" + "terraform": "file:../../packages/terraform", + "validator": "^13.12.0" }, "devDependencies": { "@types/react": "^18.3.11", diff --git a/apps/client/src/components/Layout/Header/ActionsButtons.tsx b/apps/client/src/components/Layout/Header/ActionsButtons.tsx index 958ca7a0..b3a43c01 100644 --- a/apps/client/src/components/Layout/Header/ActionsButtons.tsx +++ b/apps/client/src/components/Layout/Header/ActionsButtons.tsx @@ -1,4 +1,5 @@ import { urls } from '@/src/apis'; +import { getPropertyFilters } from '@/src/models/ncloud'; import { ServerRequiredFields } from '@/src/models/ncloud/Server'; import { transformObject, validateObject } from '@/src/models/ncloud/utils'; import CodeDrawer from '@components/CodeDrawer'; @@ -53,21 +54,53 @@ export default () => { }, }); + const CURRENT_ALLOWED_TYPES = ['server', 'object-storage', 'db-mysql']; + const validateResource = ( + resources: { + type: string; + properties: any; + }[], + ) => { + const validResult: { type: string; isValid: boolean }[] = []; + resources.forEach((resource) => { + if ( + !validateObject( + resource.properties, + getPropertyFilters(resource.type), + ) + ) { + validResult.push({ type: resource.type, isValid: false }); + } + }); + + return validResult; + }; const handleConvertTerraform = () => { - if (!selectedResource) return; - const nodeProperties = { - type: selectedResource.type, - name: selectedResource.properties.name, - properties: transformObject(selectedResource.properties), - }; + let resources = selectedResource + ? [ + { + type: selectedResource.type, + properties: transformObject(selectedResource.properties), + }, + ] + : Object.values(nodes) + .filter((node) => CURRENT_ALLOWED_TYPES.includes(node.type)) + .map((node) => ({ + type: node.type, + properties: transformObject(node.properties), + })); - if (!validateObject(nodeProperties.properties, ServerRequiredFields)) { - alert('Is not valid'); + const validResult = validateResource(resources); + const isValid = validResult.every((result) => result.isValid); + if (!isValid) { + validResult.forEach((result) => { + alert(`${result.type} is not valid properties`); + }); return; } const Converter = new TerraformConverter(); - Converter.addResourceFromJson([nodeProperties]); + Converter.addResourceFromJson(resources); setTerraformCode(Converter.generate()); setOpenDrawer(true); }; @@ -76,7 +109,6 @@ export default () => { saveArchitecture(); }; - console.log(open); return ( <> diff --git a/apps/client/src/components/NCloud/PropertiesBar/MySQLDBProperties.tsx b/apps/client/src/components/NCloud/PropertiesBar/MySQLDBProperties.tsx new file mode 100644 index 00000000..6b85e8e0 --- /dev/null +++ b/apps/client/src/components/NCloud/PropertiesBar/MySQLDBProperties.tsx @@ -0,0 +1,173 @@ +import { + MYSQLDBProp, + validateMySQLDB, + ValidationErrors, +} from '@/src/models/ncloud/MySQLDB'; +import { NETWORKS_CATEGORIES } from '@/src/models/ncloud/Networks'; +import useNCloud from '@hooks/useNCloud'; +import { FormHelperText } from '@mui/material'; +import Divider from '@mui/material/Divider'; +import FormControl from '@mui/material/FormControl'; +import Input from '@mui/material/Input'; +import InputLabel from '@mui/material/InputLabel'; +import Stack from '@mui/material/Stack'; +import { useEffect, useState } from 'react'; +import validator from 'validator'; + +type Props = {}; + +export default ({}: Props) => { + const { selectedResource, updateProperties } = useNCloud(); + + const [properties, setProperties] = useState({ + serverName: '', + serverNamePrefix: '', + userName: '', + userPassword: '', + hostIp: '', + databaseName: '', + serviceName: '', + }); + + const [errors, setErrors] = useState({}); + + useEffect(() => { + if (!selectedResource) return; + const { properties } = selectedResource; + const { + serverName, + serverNamePrefix, + userName, + userPassword, + hostIp, + serviceName, + databaseName, + } = properties; + setProperties((prev) => ({ + ...prev, + serverName, + serverNamePrefix, + userName, + serviceName, + userPassword, + hostIp: '192.168.0.1', + databaseName, + })); + }, [selectedResource]); + + // 192.168.0.01 임시용 + const handleChange = (e: React.ChangeEvent, type: string) => { + const value = (e.target as HTMLInputElement).value; + if (!selectedResource) return; + const updatedProperties = { + ...properties, + [type]: value, + }; + setProperties(updatedProperties); + updateProperties(selectedResource.id, { + [type]: value, + hostIp: '192.168.0.1', + }); + + const currentErrors = validateMySQLDB(updatedProperties); + setErrors(currentErrors); + }; + + const propertiesWithoutNetworks = Object.keys( + selectedResource?.properties ?? {}, + ).filter((prop) => !NETWORKS_CATEGORIES.includes(prop)); + + const getLabel = (prop: string) => { + switch (prop) { + case 'serverName': + return `Server Name`; + case 'serverNamePrefix': + return `Server Name Prefix`; + case 'userName': + return `User Name`; + case 'userPassword': + return `User Password`; + case 'hostIp': + return `Host IP`; + case 'databaseName': + return `Database Name`; + case 'serviceName': + return 'Service Name'; + default: + return 'Unsupport Type'; + } + }; + + return ( + + } + spacing={2} + > + {propertiesWithoutNetworks.slice(0, 4).map((prop) => ( + + {getLabel(prop)} + e.stopPropagation()} + onChange={(e) => handleChange(e, prop)} + /> + {errors[prop as keyof MYSQLDBProp] && ( + + {errors[prop as keyof MYSQLDBProp]} + + )} + + ))} + + } + spacing={2} + > + {propertiesWithoutNetworks.slice(4).map((prop) => ( + + {getLabel(prop)} + e.stopPropagation()} + onChange={(e) => handleChange(e, prop)} + /> + {errors[prop as keyof MYSQLDBProp] && ( + + {errors[prop as keyof MYSQLDBProp]} + + )} + + ))} + + + ); +}; diff --git a/apps/client/src/components/NCloud/PropertiesBar/ObjectStorageProperties.tsx b/apps/client/src/components/NCloud/PropertiesBar/ObjectStorageProperties.tsx new file mode 100644 index 00000000..faf24aa9 --- /dev/null +++ b/apps/client/src/components/NCloud/PropertiesBar/ObjectStorageProperties.tsx @@ -0,0 +1,53 @@ +import useNCloud from '@hooks/useNCloud'; +import Divider from '@mui/material/Divider'; +import FormControl from '@mui/material/FormControl'; +import Input from '@mui/material/Input'; +import InputLabel from '@mui/material/InputLabel'; +import Stack from '@mui/material/Stack'; +import { useEffect, useState } from 'react'; + +type Props = {}; + +export default ({}: Props) => { + const { selectedResource, updateProperties } = useNCloud(); + + const [bucketName, setBucketName] = useState(''); + + useEffect(() => { + if (!selectedResource) return; + const { properties } = selectedResource; + setBucketName(properties.bucketName ?? ''); + }, [selectedResource]); + + const handleChangeName = (e: React.ChangeEvent) => { + const newName = e.target.value; + setBucketName(newName); + if (!selectedResource) return; + updateProperties(selectedResource.id, { bucketName: newName }); + }; + + return ( + } + spacing={2} + > + + Resource Name + e.stopPropagation()} + onChange={handleChangeName} + /> + + + ); +}; diff --git a/apps/client/src/components/NCloud/PropertiesBar/index.tsx b/apps/client/src/components/NCloud/PropertiesBar/index.tsx index 15f52d9e..59b9e881 100644 --- a/apps/client/src/components/NCloud/PropertiesBar/index.tsx +++ b/apps/client/src/components/NCloud/PropertiesBar/index.tsx @@ -1,12 +1,18 @@ import ServerProperties from '@components/NCloud/PropertiesBar/ServerProperties'; import useNCloud from '@hooks/useNCloud'; import { AppBar, Toolbar, Typography } from '@mui/material'; +import ObjectStorageProperties from './ObjectStorageProperties'; +import MySQLDBProperties from './MySQLDBProperties'; const PropertiesFactory = (type: string) => { switch (type) { case 'server': { return ; } + case 'object-storage': + return ; + case 'db-mysql': + return ; default: { return ( >; + +export const validateMySQLDB = ( + json: Partial, +): ValidationErrors => { + const errors: ValidationErrors = {}; + + if ( + !json.serviceName || + !validator.isLength(json.serviceName, { min: 3, max: 30 }) + ) { + errors.serviceName = 'Service Name 3-30 characters'; + } + + if ( + !json.serverNamePrefix || + !validator.isLength(json.serverNamePrefix, { min: 3, max: 20 }) + ) { + errors.serverNamePrefix = 'Server Name Prefix 3-20 characters'; + } + + if ( + !json.userName || + !validator.isLength(json.userName, { min: 4, max: 16 }) + ) { + errors.userName = 'User Name 4-16 characters'; + } + + if ( + !json.userPassword || + !validator.isLength(json.userPassword, { min: 8, max: 20 }) + ) { + errors.userPassword = 'User Password 8-20 characters'; + } + + if (!json.hostIp || !validator.isIP(json.hostIp, 4)) { + errors.hostIp = 'Host IP must be a valid IPv4 address'; + } + + if ( + !json.databaseName || + !validator.isLength(json.databaseName, { min: 1, max: 30 }) + ) { + errors.databaseName = 'Database Name 1-30 characters'; + } + + return errors; +}; diff --git a/apps/client/src/models/ncloud/Networks.ts b/apps/client/src/models/ncloud/Networks.ts index bc28790a..c03bfda1 100644 --- a/apps/client/src/models/ncloud/Networks.ts +++ b/apps/client/src/models/ncloud/Networks.ts @@ -42,3 +42,10 @@ export const SubnetGroup: Group = { name: '', }, }; + +export const NETWORKS_CATEGORIES = [ + 'region', + 'vpc', + 'subnet', + // 'security-group', +]; diff --git a/apps/client/src/models/ncloud/ObjectStorage.ts b/apps/client/src/models/ncloud/ObjectStorage.ts index 9a034495..df8cc0a7 100644 --- a/apps/client/src/models/ncloud/ObjectStorage.ts +++ b/apps/client/src/models/ncloud/ObjectStorage.ts @@ -3,7 +3,7 @@ import { Node } from '@types'; import { Networks, NetworksProp } from './Networks'; export interface ObjectStorageProp extends NetworksProp { - //TODO: + bucketName?: string; } export const ObjectStorageNode: Node & { @@ -17,5 +17,13 @@ export const ObjectStorageNode: Node & { }, properties: { ...Networks, + bucketName: undefined, }, }; + +export const ObjectStorageRequiredFields = { + bucketName: true, + vpc: true, + subnet: true, + region: true, +}; diff --git a/apps/client/src/models/ncloud/Server.ts b/apps/client/src/models/ncloud/Server.ts index 3a12634d..060b4537 100644 --- a/apps/client/src/models/ncloud/Server.ts +++ b/apps/client/src/models/ncloud/Server.ts @@ -29,4 +29,7 @@ export const ServerRequiredFields = { name: true, server_image_number: true, server_spec_code: true, + vpc: true, + subnet: true, + region: true, }; diff --git a/apps/client/src/models/ncloud/index.ts b/apps/client/src/models/ncloud/index.ts index d8674af7..5c046092 100644 --- a/apps/client/src/models/ncloud/index.ts +++ b/apps/client/src/models/ncloud/index.ts @@ -1,11 +1,14 @@ import { CloudFunctionNode } from './CloudFunction'; import { ContainerRegistryNode } from './ContainerRegistry'; import { LoadBalancerNode } from './LoadBalancer'; -import { MySQLDBNode } from './MySQLDB'; +import { MySQLDBNode, MySQLDBRequiredFields } from './MySQLDB'; import { NatGatewayNode } from './NatGateway'; import { RegionGroup, SubnetGroup, VpcGroup } from './Networks'; -import { ObjectStorageNode } from './ObjectStorage'; -import { ServerNode } from './Server'; +import { + ObjectStorageNode, + ObjectStorageRequiredFields, +} from './ObjectStorage'; +import { ServerNode, ServerRequiredFields } from './Server'; export const NcloudNodeFactory = (type: string) => { switch (type) { @@ -43,3 +46,16 @@ export const NcloudGroupFactory = (type: string) => { } } }; + +export const getPropertyFilters = (type: string) => { + switch (type) { + case 'server': + return ServerRequiredFields; + case 'object-storage': + return ObjectStorageRequiredFields; + case 'db-mysql': + return MySQLDBRequiredFields; + default: + return {}; + } +}; diff --git a/packages/terraform/util/resource.ts b/packages/terraform/util/resource.ts index 87b53e5c..4cefab76 100644 --- a/packages/terraform/util/resource.ts +++ b/packages/terraform/util/resource.ts @@ -10,7 +10,7 @@ import { export const processDependencies = (node: any): any[] => { if ( - !['server', 'loadbalancer', 'mysql', 'redis'].includes( + !['server', 'loadbalancer', 'db-mysql', 'redis'].includes( node.type.toLowerCase(), ) ) @@ -42,8 +42,8 @@ export const processDependencies = (node: any): any[] => { return dependencies; }; -export const processNodes = (nodes: any[]): any[] => - nodes.reduce( +export const processNodes = (nodes: any[]): any[] => { + return nodes.reduce( (acc: CloudCanvasNode[], node) => [ ...acc, ...processDependencies(node), @@ -51,3 +51,4 @@ export const processNodes = (nodes: any[]): any[] => ], [], ); +}; diff --git a/packages/terraform/util/resourceParser.ts b/packages/terraform/util/resourceParser.ts index 9ffb5472..433232fd 100644 --- a/packages/terraform/util/resourceParser.ts +++ b/packages/terraform/util/resourceParser.ts @@ -15,24 +15,24 @@ import { NCloudObjectStorageBucket } from '../model/NCloudObjectStorageBucket'; import { NCloudRedis } from '../model/NCloudRedis'; export function parseToNCloudModel(resource: any): NCloudModel { - const { type, name, properties } = resource; + const { type, properties } = resource; switch (type.toLowerCase()) { case 'vpc': return new NCloudVPC({ - name: name || 'vpc', + name: properties.name || 'vpc', ipv4CidrBlock: properties.cidrBlock, }); case 'networkacl': return new NCloudNetworkACL({ - name: name || 'nacl', + name: properties.name || 'nacl', vpcName: properties.vpcName, }); case 'subnet': return new NCloudSubnet({ - name: name || 'subnet', + name: properties.name || 'subnet', subnet: properties.subnet, zone: properties.zone, subnetType: properties.subnetType, @@ -44,7 +44,7 @@ export function parseToNCloudModel(resource: any): NCloudModel { case 'acg': case 'accesscontrolgroup': return new NCloudACG({ - name: name || 'acg', + name: properties.name || 'acg', description: properties.description, vpcName: properties.vpcName, }); @@ -62,19 +62,19 @@ export function parseToNCloudModel(resource: any): NCloudModel { case 'loginkey': return new NCloudLoginKey({ - name: name || 'login-key', + name: properties.name || 'login-key', }); case 'networkinterface': return new NCloudNetworkInterface({ - name: name || 'nic', + name: properties.name || 'nic', subnetName: properties.subnetName, acgName: properties.acgName, }); case 'server': return new NCloudServer({ - name: name || 'server', + name: properties.name || 'server', serverImageNumber: properties.server_image_number, serverSpecCode: properties.server_spec_code, subnetName: properties.subnet, @@ -82,14 +82,14 @@ export function parseToNCloudModel(resource: any): NCloudModel { case 'publicip': return new NCloudPublicIP({ - name: name || 'public-ip', + name: properties.name || 'public-ip', description: properties.description, serverName: properties.serverName, }); case 'loadbalancer': return new NCloudLoadBalancer({ - name: name || 'load-balancer', + name: properties.name || 'load-balancer', networkType: properties.networkType, type: properties.type, subnetName: properties.subnet, @@ -98,12 +98,12 @@ export function parseToNCloudModel(resource: any): NCloudModel { case 'launchconfiguration': return new NCloudLaunchConfiguration({ - name: name || 'launch-config', + name: properties.name || 'launch-config', serverImageProductCode: properties.serverImageProductCode, serverProductCode: properties.serverProductCode, }); - case 'mysql': + case 'db-mysql': if ( !properties.serverNamePrefix || !properties.userName || @@ -116,7 +116,7 @@ export function parseToNCloudModel(resource: any): NCloudModel { ); } return new NCloudMySQL({ - serviceName: name || 'mysql', + serviceName: properties.serviceName || 'mysql', serverNamePrefix: properties.serverNamePrefix, userName: properties.userName, userPassword: properties.userPassword, @@ -126,14 +126,14 @@ export function parseToNCloudModel(resource: any): NCloudModel { vpc: properties.vpc, }); - case 'objectstoragebucket': + case 'object-storage': return new NCloudObjectStorageBucket({ bucketName: properties.bucketName, }); case 'redis': return new NCloudRedis({ - serviceName: name || 'redis', + serviceName: properties.serviceName || 'redis', serverNamePrefix: properties.serverNamePrefix, vpcNo: properties.vpc, subnetNo: properties.subnet, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7503885b..007e1069 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -47,6 +47,9 @@ importers: '@mui/material': specifier: ^6.1.5 version: 6.1.6(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react@18.3.1))(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@types/validator': + specifier: ^13.12.2 + version: 13.12.2 nanoid: specifier: ^5.0.8 version: 5.0.8 @@ -56,6 +59,12 @@ importers: react-dom: specifier: ^18.3.1 version: 18.3.1(react@18.3.1) + react-router-dom: + specifier: ^7.0.1 + version: 7.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-select: + specifier: ^5.8.3 + version: 5.8.3(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-syntax-highlighter: specifier: ^15.6.1 version: 15.6.1(react@18.3.1) @@ -65,6 +74,9 @@ importers: terraform: specifier: file:../../packages/terraform version: link:../../packages/terraform + validator: + specifier: ^13.12.0 + version: 13.12.0 devDependencies: '@types/react': specifier: ^18.3.11 @@ -1206,6 +1218,15 @@ packages: resolution: {integrity: sha512-ulqQu4KMr1/sTFIYvqSdegHT8NIkt66tFAkugGnHA+1WAfEn6hMzNR+svjXGFRVLnapxvej67Z/LwchFrnLBUg==} engines: {node: '>=18.0.0', npm: '>=9.0.0'} + '@floating-ui/core@1.6.8': + resolution: {integrity: sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==} + + '@floating-ui/dom@1.6.12': + resolution: {integrity: sha512-NP83c0HjokcGVEMeoStg317VD9W7eDlGK7457dMBANbKA6GJZdc7rjujdgqzTaz93jkGgc5P/jeWbaCHnMNc+w==} + + '@floating-ui/utils@0.2.8': + resolution: {integrity: sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==} + '@humanwhocodes/config-array@0.13.0': resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} engines: {node: '>=10.10.0'} @@ -2053,6 +2074,9 @@ packages: '@types/cookie-parser@1.4.7': resolution: {integrity: sha512-Fvuyi354Z+uayxzIGCwYTayFKocfV7TuDYZClCdIP9ckhvAu/ixDtCB6qx2TT0FKjPLf1f3P/J1rgf6lPs64mw==} + '@types/cookie@0.6.0': + resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} + '@types/cookiejar@2.1.5': resolution: {integrity: sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==} @@ -2944,6 +2968,10 @@ packages: resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} + cookie@1.0.2: + resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} + engines: {node: '>=18'} + cookiejar@2.1.4: resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==} @@ -4576,6 +4604,9 @@ packages: resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==} engines: {node: '>= 4.0.0'} + memoize-one@6.0.0: + resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==} + merge-descriptors@1.0.3: resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} @@ -5187,6 +5218,29 @@ packages: resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} engines: {node: '>=0.10.0'} + react-router-dom@7.0.1: + resolution: {integrity: sha512-duBzwAAiIabhFPZfDjcYpJ+f08TMbPMETgq254GWne2NW1ZwRHhZLj7tpSp8KGb7JvZzlLcjGUnqLxpZQVEPng==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + + react-router@7.0.1: + resolution: {integrity: sha512-WVAhv9oWCNsja5AkK6KLpXJDSJCQizOIyOd4vvB/+eHGbYx5vkhcmcmwWjQ9yqkRClogi+xjEg9fNEOd5EX/tw==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + peerDependenciesMeta: + react-dom: + optional: true + + react-select@5.8.3: + resolution: {integrity: sha512-lVswnIq8/iTj1db7XCG74M/3fbGB6ZaluCzvwPGT5ZOjCdL/k0CLWhEK0vCBLuU5bHTEf6Gj8jtSvi+3v+tO1w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-syntax-highlighter@15.6.1: resolution: {integrity: sha512-OqJ2/vL7lEeV5zTJyG7kmARppUjiB9h9udl4qHQjjgEos66z00Ia0OckwYfRxCSFrW8RJIBnsBwQsHZbVPspqg==} peerDependencies: @@ -5421,6 +5475,9 @@ packages: engines: {node: '>= 14'} hasBin: true + set-cookie-parser@2.7.1: + resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} + set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -5932,6 +5989,9 @@ packages: cpu: [arm64] os: [linux] + turbo-stream@2.4.0: + resolution: {integrity: sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==} + turbo-windows-64@2.2.3: resolution: {integrity: sha512-NPrjacrZypMBF31b4HE4ROg4P3nhMBPHKS5WTpMwf7wydZ8uvdEHpESVNMOtqhlp857zbnKYgP+yJF30H3N2dQ==} cpu: [x64] @@ -6042,6 +6102,15 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + use-isomorphic-layout-effect@1.1.2: + resolution: {integrity: sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + use-sync-external-store@1.2.2: resolution: {integrity: sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==} peerDependencies: @@ -7051,6 +7120,17 @@ snapshots: '@faker-js/faker@9.2.0': {} + '@floating-ui/core@1.6.8': + dependencies: + '@floating-ui/utils': 0.2.8 + + '@floating-ui/dom@1.6.12': + dependencies: + '@floating-ui/core': 1.6.8 + '@floating-ui/utils': 0.2.8 + + '@floating-ui/utils@0.2.8': {} + '@humanwhocodes/config-array@0.13.0': dependencies: '@humanwhocodes/object-schema': 2.0.3 @@ -7894,6 +7974,8 @@ snapshots: dependencies: '@types/express': 4.17.21 + '@types/cookie@0.6.0': {} + '@types/cookiejar@2.1.5': {} '@types/crypto-js@4.2.2': {} @@ -8940,6 +9022,8 @@ snapshots: cookie@0.7.2: {} + cookie@1.0.2: {} + cookiejar@2.1.4: {} core-util-is@1.0.3: {} @@ -9413,8 +9497,8 @@ snapshots: '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.6.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1) eslint-plugin-react: 7.37.2(eslint@8.57.1) eslint-plugin-react-hooks: 5.0.0(eslint@8.57.1) @@ -9437,37 +9521,37 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1): + eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.3.7 enhanced-resolve: 5.17.1 eslint: 8.57.1 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1) fast-glob: 3.3.2 get-tsconfig: 4.8.1 is-bun-module: 1.2.1 is-glob: 4.0.3 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) transitivePeerDependencies: - '@typescript-eslint/parser' - eslint-import-resolver-node - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): + eslint-module-utils@2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.6.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -9478,7 +9562,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -11047,6 +11131,8 @@ snapshots: dependencies: fs-monkey: 1.0.6 + memoize-one@6.0.0: {} + merge-descriptors@1.0.3: {} merge-stream@2.0.0: {} @@ -11573,6 +11659,39 @@ snapshots: react-refresh@0.14.2: {} + react-router-dom@7.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-router: 7.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + + react-router@7.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@types/cookie': 0.6.0 + cookie: 1.0.2 + react: 18.3.1 + set-cookie-parser: 2.7.1 + turbo-stream: 2.4.0 + optionalDependencies: + react-dom: 18.3.1(react@18.3.1) + + react-select@5.8.3(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.0 + '@emotion/cache': 11.13.1 + '@emotion/react': 11.13.3(@types/react@18.3.12)(react@18.3.1) + '@floating-ui/dom': 1.6.12 + '@types/react-transition-group': 4.4.11 + memoize-one: 6.0.0 + prop-types: 15.8.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + use-isomorphic-layout-effect: 1.1.2(@types/react@18.3.12)(react@18.3.1) + transitivePeerDependencies: + - '@types/react' + - supports-color + react-syntax-highlighter@15.6.1(react@18.3.1): dependencies: '@babel/runtime': 7.26.0 @@ -11875,6 +11994,8 @@ snapshots: transitivePeerDependencies: - supports-color + set-cookie-parser@2.7.1: {} + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -12469,6 +12590,8 @@ snapshots: turbo-linux-arm64@2.2.3: optional: true + turbo-stream@2.4.0: {} + turbo-windows-64@2.2.3: optional: true @@ -12588,6 +12711,12 @@ snapshots: dependencies: punycode: 2.3.1 + use-isomorphic-layout-effect@1.1.2(@types/react@18.3.12)(react@18.3.1): + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.12 + use-sync-external-store@1.2.2(react@19.0.0-rc-66855b96-20241106): dependencies: react: 19.0.0-rc-66855b96-20241106 From a1887dae0a69b0767e8abae257259f4905fbcd76 Mon Sep 17 00:00:00 2001 From: Geonhyuk Seo Date: Tue, 3 Dec 2024 13:13:01 +0900 Subject: [PATCH 085/124] =?UTF-8?q?=F0=9F=93=9D=20Docs(README.md):=20?= =?UTF-8?q?=EC=9D=B8=ED=94=84=EB=9D=BC=20=EC=95=84=ED=82=A4=ED=85=8D?= =?UTF-8?q?=EC=B2=98=20=EC=83=81=EC=84=B8=20=EC=84=A4=EB=AA=85=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index caee7861..248ffe40 100644 --- a/README.md +++ b/README.md @@ -172,7 +172,7 @@ Cloud Canvas는 클라우드 인프라 설계를 **그래픽 인터페이스** ### **인프라 설계** -![image](https://github.com/user-attachments/assets/893a5dab-3705-40fb-b628-70724ea278c5) +![image](https://github.com/user-attachments/assets/cd589ef7-8084-4f70-bfaf-1fabec75e746) ### **애플리케이션 설계** From 52a782da17a07b446635d7178c87f8679ea5d84e Mon Sep 17 00:00:00 2001 From: Geonhyuk Seo Date: Tue, 3 Dec 2024 13:53:33 +0900 Subject: [PATCH 086/124] =?UTF-8?q?=F0=9F=93=9D=20Docs(README.md):=20?= =?UTF-8?q?=EC=9D=B8=ED=94=84=EB=9D=BC=20=EC=95=84=ED=82=A4=ED=85=8D?= =?UTF-8?q?=EC=B2=98=EC=97=90=20=ED=85=8C=EB=9D=BC=ED=8F=BC=20=EC=9B=8C?= =?UTF-8?q?=ED=81=AC=ED=94=8C=EB=A1=9C=EC=9A=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 248ffe40..13eb06f6 100644 --- a/README.md +++ b/README.md @@ -172,6 +172,8 @@ Cloud Canvas는 클라우드 인프라 설계를 **그래픽 인터페이스** ### **인프라 설계** +![image](https://github.com/user-attachments/assets/ecdfaaa9-4d2e-4ef3-ac2e-080a63e7fe66) + ![image](https://github.com/user-attachments/assets/cd589ef7-8084-4f70-bfaf-1fabec75e746) ### **애플리케이션 설계** From fce8f0b6ab8c4e50d7101cf5241a5c1cd2a9cbb3 Mon Sep 17 00:00:00 2001 From: Geonhyuk Seo Date: Tue, 3 Dec 2024 14:36:46 +0900 Subject: [PATCH 087/124] =?UTF-8?q?=F0=9F=93=9D=20Docs(README.md):=201?= =?UTF-8?q?=EC=B0=A8=EC=A0=81=EC=9C=BC=EB=A1=9C=20=EC=95=A0=ED=94=8C?= =?UTF-8?q?=EB=A6=AC=EC=BC=80=EC=9D=B4=EC=85=98=20=EC=95=84=ED=82=A4?= =?UTF-8?q?=ED=85=8D=EC=B2=98=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 13eb06f6..07bdb115 100644 --- a/README.md +++ b/README.md @@ -178,7 +178,7 @@ Cloud Canvas는 클라우드 인프라 설계를 **그래픽 인터페이스** ### **애플리케이션 설계** -![image](https://github.com/user-attachments/assets/0b28bcd3-1cf1-41d0-9e81-59796bb8e66e) +![image](https://github.com/user-attachments/assets/93ce4bd2-933a-40aa-bc14-8bb6429723d3) ### **CI/CD 파이프라인** From f4c7968253ce5b868d7e1299c5c0561e80913b6a Mon Sep 17 00:00:00 2001 From: Geonhyuk Seo Date: Tue, 3 Dec 2024 15:38:41 +0900 Subject: [PATCH 088/124] =?UTF-8?q?=F0=9F=93=9D=20Docs(README.md):=20?= =?UTF-8?q?=EC=9D=B8=ED=94=84=EB=9D=BC=20=EC=95=84=ED=82=A4=ED=85=8D?= =?UTF-8?q?=EC=B2=98=EC=97=90=20CI/CD=20=EA=B7=B8=EB=A3=B9=ED=95=91=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 07bdb115..f18f0911 100644 --- a/README.md +++ b/README.md @@ -174,7 +174,7 @@ Cloud Canvas는 클라우드 인프라 설계를 **그래픽 인터페이스** ![image](https://github.com/user-attachments/assets/ecdfaaa9-4d2e-4ef3-ac2e-080a63e7fe66) -![image](https://github.com/user-attachments/assets/cd589ef7-8084-4f70-bfaf-1fabec75e746) +![image](https://github.com/user-attachments/assets/b18b1048-5fe8-43ee-a33f-8fbe2b38e873) ### **애플리케이션 설계** From 52c1ecf95aae423efff69dfb07ecae396646e25e Mon Sep 17 00:00:00 2001 From: Geonhyuk Seo Date: Tue, 3 Dec 2024 15:40:14 +0900 Subject: [PATCH 089/124] =?UTF-8?q?=F0=9F=93=9D=20Docs(README.md):=20?= =?UTF-8?q?=EA=B8=B0=EC=A1=B4=20CI/CD=20=ED=8C=8C=EC=9D=B4=ED=94=84?= =?UTF-8?q?=EB=9D=BC=EC=9D=B8=20=EC=84=B9=EC=85=98=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index f18f0911..7b6983c6 100644 --- a/README.md +++ b/README.md @@ -180,10 +180,6 @@ Cloud Canvas는 클라우드 인프라 설계를 **그래픽 인터페이스** ![image](https://github.com/user-attachments/assets/93ce4bd2-933a-40aa-bc14-8bb6429723d3) -### **CI/CD 파이프라인** - -cicd - ## 우리의 Next! ## 🌈 **함께하세요!** From f74ffc0680dc03c3e433ee3b4123817ecd2fd898 Mon Sep 17 00:00:00 2001 From: Geonhyuk Seo Date: Tue, 3 Dec 2024 15:48:10 +0900 Subject: [PATCH 090/124] =?UTF-8?q?=F0=9F=93=9D=20Docs(README.md):=20terra?= =?UTF-8?q?form=20workflow=20=EC=95=84=ED=82=A4=ED=85=8D=EC=B2=98=20?= =?UTF-8?q?=ED=81=B4=EB=9D=BC=EC=9A=B0=EB=93=9C=20=EB=AA=85=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7b6983c6..69face5a 100644 --- a/README.md +++ b/README.md @@ -172,7 +172,7 @@ Cloud Canvas는 클라우드 인프라 설계를 **그래픽 인터페이스** ### **인프라 설계** -![image](https://github.com/user-attachments/assets/ecdfaaa9-4d2e-4ef3-ac2e-080a63e7fe66) +![image](https://github.com/user-attachments/assets/e8bd555e-ae84-4989-a520-800a61b3da54) ![image](https://github.com/user-attachments/assets/b18b1048-5fe8-43ee-a33f-8fbe2b38e873) From 883fc43f409332fbb03e3c1110b8997d0a055436 Mon Sep 17 00:00:00 2001 From: Geonhyuk Seo Date: Tue, 3 Dec 2024 16:27:16 +0900 Subject: [PATCH 091/124] =?UTF-8?q?=F0=9F=93=9D=20Docs(README.md):=20?= =?UTF-8?q?=EC=95=A0=ED=94=8C=EB=A6=AC=EC=BC=80=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EC=95=84=ED=82=A4=ED=85=8D=EC=B2=98=20=EC=99=84=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 69face5a..0647a0bd 100644 --- a/README.md +++ b/README.md @@ -178,7 +178,7 @@ Cloud Canvas는 클라우드 인프라 설계를 **그래픽 인터페이스** ### **애플리케이션 설계** -![image](https://github.com/user-attachments/assets/93ce4bd2-933a-40aa-bc14-8bb6429723d3) +![image](https://github.com/user-attachments/assets/04145d8b-61b0-401a-8943-7494a0f9aed5) ## 우리의 Next! From 11ef155b946afc399f30fc11196ab78e3ac935d5 Mon Sep 17 00:00:00 2001 From: Geonhyuk Seo Date: Tue, 3 Dec 2024 16:32:26 +0900 Subject: [PATCH 092/124] =?UTF-8?q?=F0=9F=93=9D=20Docs(README.md):=20?= =?UTF-8?q?=ED=94=84=EB=A1=A0=ED=8A=B8=20=EA=B8=B0=EC=88=A0=20=EC=8A=A4?= =?UTF-8?q?=ED=83=9D=EC=97=90=20tailwind=20css=20=EB=B1=83=EC=A7=80=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 0647a0bd..324e1147 100644 --- a/README.md +++ b/README.md @@ -122,6 +122,7 @@ Cloud Canvas는 클라우드 인프라 설계를 **그래픽 인터페이스**

    Next.js React + Tailwind CSS

    ### 🔧 Backend From 0d54d5290ad341af2524fac2ed5713d06a37bf27 Mon Sep 17 00:00:00 2001 From: Geonhyuk Seo Date: Tue, 3 Dec 2024 16:40:10 +0900 Subject: [PATCH 093/124] =?UTF-8?q?=F0=9F=93=9D=20Docs(README.md):=20?= =?UTF-8?q?=EA=B8=B0=EC=88=A0=20=EC=8A=A4=ED=83=9D=20=EB=B1=83=EC=A7=80=20?= =?UTF-8?q?=EC=B5=9C=EC=A2=85=EC=A0=81=EC=9D=B8=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 324e1147..e4e683cc 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,8 @@ Cloud Canvas는 클라우드 인프라 설계를 **그래픽 인터페이스**

    JavaScript TypeScript + Prettier + ESLint

    ### 🖥️ Frontend @@ -123,6 +125,8 @@ Cloud Canvas는 클라우드 인프라 설계를 **그래픽 인터페이스** Next.js React Tailwind CSS + Vite + TanStack Query

    ### 🔧 Backend From 63ed01c5fc499f1033b351a1948ecfec79ed05e0 Mon Sep 17 00:00:00 2001 From: paulcjy Date: Tue, 3 Dec 2024 16:42:53 +0900 Subject: [PATCH 094/124] =?UTF-8?q?=F0=9F=90=9E=20Fix(server):=20create=20?= =?UTF-8?q?public=20architecture=20dto=EC=97=90=20=EC=98=A4=ED=83=80=20?= =?UTF-8?q?=EC=88=98=EC=A0=95(tag=20-\>=20tags)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../public-architecture/dto/create-public-architecture.dto.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/server/src/public-architecture/dto/create-public-architecture.dto.ts b/apps/server/src/public-architecture/dto/create-public-architecture.dto.ts index e1a6b884..9ed0ebac 100644 --- a/apps/server/src/public-architecture/dto/create-public-architecture.dto.ts +++ b/apps/server/src/public-architecture/dto/create-public-architecture.dto.ts @@ -23,5 +23,5 @@ export class CreatePublicArchitectureDto { @IsOptional() @IsArray() @IsString({ each: true }) - tag?: string[]; + tags?: string[]; } From 877e2f5f79d5b35866034b3a361504137ad39916 Mon Sep 17 00:00:00 2001 From: Geonhyuk Seo Date: Tue, 3 Dec 2024 16:44:07 +0900 Subject: [PATCH 095/124] =?UTF-8?q?=F0=9F=93=9D=20Docs(README.md):=20?= =?UTF-8?q?=EC=A7=84=EC=A7=9C=20=EC=B5=9C=EC=A2=85=20=EA=B8=B0=EC=88=A0=20?= =?UTF-8?q?=EC=8A=A4=ED=83=9D=20=EC=95=84=EC=9D=B4=EC=BD=98=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e4e683cc..a21fe9b0 100644 --- a/README.md +++ b/README.md @@ -147,12 +147,14 @@ Cloud Canvas는 클라우드 인프라 설계를 **그래픽 인터페이스** Docker Compose GitHub Actions Naver Cloud + Nginx

    -### 🔍 DevOps & Logging +### 🔍 DevOps & Monitoring

    Terraform + Terraform Cloud Elasticsearch FluentD Kibana From 06fed6b5255a0777dd4688509c62bda2df11138a Mon Sep 17 00:00:00 2001 From: paulcjy Date: Tue, 3 Dec 2024 16:44:47 +0900 Subject: [PATCH 096/124] =?UTF-8?q?=E2=9C=A8=20Feat(hub):=20=EB=A1=9C?= =?UTF-8?q?=EA=B3=A0=20=EC=A0=9C=EC=9E=91=20=EB=B0=8F=20=EB=A1=9C=EA=B3=A0?= =?UTF-8?q?=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/hub/src/app/favicon.ico | Bin 0 -> 181556 bytes apps/hub/src/ui/CloudCanvasIcon.tsx | 110 ++++++++++++++++++++++++++++ apps/hub/src/ui/logo.svg | 106 +++++++++++++++++++++++++++ 3 files changed, 216 insertions(+) create mode 100644 apps/hub/src/app/favicon.ico create mode 100644 apps/hub/src/ui/CloudCanvasIcon.tsx create mode 100644 apps/hub/src/ui/logo.svg diff --git a/apps/hub/src/app/favicon.ico b/apps/hub/src/app/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..356af84cc2fd6e3288bd747f610051667f8b9173 GIT binary patch literal 181556 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%0DHf_q^)dSc@GXO1 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 literal 0 HcmV?d00001 diff --git a/apps/hub/src/ui/CloudCanvasIcon.tsx b/apps/hub/src/ui/CloudCanvasIcon.tsx new file mode 100644 index 00000000..8708b985 --- /dev/null +++ b/apps/hub/src/ui/CloudCanvasIcon.tsx @@ -0,0 +1,110 @@ +export const CloudCanvasIcon = ({ width = 40 }: { width?: number }) => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/apps/hub/src/ui/logo.svg b/apps/hub/src/ui/logo.svg new file mode 100644 index 00000000..691d44dc --- /dev/null +++ b/apps/hub/src/ui/logo.svg @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From f836e69f9ce33174d816202b53471b5869159ff0 Mon Sep 17 00:00:00 2001 From: paulcjy Date: Tue, 3 Dec 2024 16:45:29 +0900 Subject: [PATCH 097/124] =?UTF-8?q?=F0=9F=90=9B=20Design(hub):=20=EB=B2=84?= =?UTF-8?q?=ED=8A=BC=EC=97=90=20=ED=98=B8=EB=B2=84=20=ED=9A=A8=EA=B3=BC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/hub/src/components/SearchBar/index.tsx | 2 +- apps/hub/src/ui/Button.tsx | 2 +- apps/hub/src/ui/LinkButton.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/hub/src/components/SearchBar/index.tsx b/apps/hub/src/components/SearchBar/index.tsx index a7d776ed..8101e9ef 100644 --- a/apps/hub/src/components/SearchBar/index.tsx +++ b/apps/hub/src/components/SearchBar/index.tsx @@ -25,7 +25,7 @@ export const SearchBar = ({ /> diff --git a/apps/hub/src/ui/Button.tsx b/apps/hub/src/ui/Button.tsx index 3c576cf1..ff9cf59c 100644 --- a/apps/hub/src/ui/Button.tsx +++ b/apps/hub/src/ui/Button.tsx @@ -9,7 +9,7 @@ export const Button = ({ }) => { return (

    -

    {title}

    - -
    -
    - by - {author} +
    +

    + {title} +

    + {isLoggedIn && ( + <> + + + + )}
    +
    +
    {new Date(createdAt).toLocaleString()}
    +
    {author}
    {imports} imported From 345bed4675eccc96fde7d11e6db009735508ba82 Mon Sep 17 00:00:00 2001 From: paulcjy Date: Tue, 3 Dec 2024 20:46:56 +0900 Subject: [PATCH 117/124] =?UTF-8?q?=E2=9C=A8=20Feat(hub):=20=ED=83=9C?= =?UTF-8?q?=EA=B7=B8=EC=97=90=EC=84=9C=20=EB=A7=81=ED=81=AC=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/hub/src/ui/Tag.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/hub/src/ui/Tag.tsx b/apps/hub/src/ui/Tag.tsx index 06715765..5c0bad35 100644 --- a/apps/hub/src/ui/Tag.tsx +++ b/apps/hub/src/ui/Tag.tsx @@ -2,8 +2,9 @@ import Link from 'next/link'; export const Tag = ({ tag }: { tag: string }) => ( {tag} From 838165f8ec8fa76785f7156f7c0212d3136e3e76 Mon Sep 17 00:00:00 2001 From: paulcjy Date: Tue, 3 Dec 2024 20:47:33 +0900 Subject: [PATCH 118/124] =?UTF-8?q?=F0=9F=90=9B=20Design(hub):=20=EC=88=98?= =?UTF-8?q?=EC=A0=95/=EC=82=AD=EC=A0=9C=20=EC=95=84=EC=9D=B4=EC=BD=98=20?= =?UTF-8?q?=EA=B8=B0=EB=B3=B8=20=ED=81=AC=EA=B8=B0=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/hub/src/ui/DeleteIcon.tsx | 7 +++---- apps/hub/src/ui/EditIcon.tsx | 7 +++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/apps/hub/src/ui/DeleteIcon.tsx b/apps/hub/src/ui/DeleteIcon.tsx index 09492e9c..6a5bc932 100644 --- a/apps/hub/src/ui/DeleteIcon.tsx +++ b/apps/hub/src/ui/DeleteIcon.tsx @@ -1,10 +1,9 @@ -export const DeleteIcon = () => ( +export const DeleteIcon = ({ size = 28 }: { size?: number }) => ( diff --git a/apps/hub/src/ui/EditIcon.tsx b/apps/hub/src/ui/EditIcon.tsx index d972d83c..7648fea0 100644 --- a/apps/hub/src/ui/EditIcon.tsx +++ b/apps/hub/src/ui/EditIcon.tsx @@ -1,10 +1,9 @@ -export const EditIcon = () => ( +export const EditIcon = ({ size = 28 }: { size?: number }) => ( From bb22860e458f5047f8414d27ddb2b9728beaaf71 Mon Sep 17 00:00:00 2001 From: paulcjy Date: Tue, 3 Dec 2024 20:49:30 +0900 Subject: [PATCH 119/124] =?UTF-8?q?=E2=9C=A8=20Feat(hub):=20public=20archi?= =?UTF-8?q?tecture=20=EC=82=AD=EC=A0=9C=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/hub/src/app/architectures/[id]/page.tsx | 28 +++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/apps/hub/src/app/architectures/[id]/page.tsx b/apps/hub/src/app/architectures/[id]/page.tsx index 9e60311e..6c98ca88 100644 --- a/apps/hub/src/app/architectures/[id]/page.tsx +++ b/apps/hub/src/app/architectures/[id]/page.tsx @@ -8,6 +8,7 @@ import { StarIcon } from '@/ui/StarIcon'; import { Tag } from '@/ui/Tag'; import { fetcher } from '@/utils/fetcher'; import { useParams } from 'next/navigation'; +import { useState } from 'react'; import useSWR from 'swr'; interface PublicArchitecture { @@ -19,6 +20,7 @@ interface PublicArchitecture { cost: number; tags: { tag: { name: string } }[]; stars: any[]; + isAuthor: boolean; _count: { stars: number; imports: number; @@ -33,7 +35,10 @@ export default function ArchitectureDetailPage() { ); if (isLoading) return ; - if (error) return ; + if (error) { + // return
    {JSON.stringify(error)}
    ; + return ; + } const { title, @@ -42,12 +47,27 @@ export default function ArchitectureDetailPage() { cost, tags, stars: starData, + isAuthor, _count: { stars, imports }, } = data!; const isStarred = starData?.length > 0; const isLoggedIn = localStorage.getItem('isLoggedIn') !== null; + const handleDelete = async () => { + const shouldDelete = confirm('삭제하시겠습니까?'); + if (!shouldDelete) return; + await fetch( + `${process.env.BACK_URL}/public-architectures/${params.id}`, + { + method: 'DELETE', + credentials: 'include', + }, + ); + alert('삭제되었습니다.'); + location.href = '/'; + }; + const toggleStar = async () => { await fetch( `${process.env.BACK_URL}/public-architectures/${params.id}/stars`, @@ -87,12 +107,12 @@ export default function ArchitectureDetailPage() {

    {title}

    - {isLoggedIn && ( + {isAuthor && ( <> - @@ -103,7 +123,7 @@ export default function ArchitectureDetailPage() {
    {new Date(createdAt).toLocaleString()}
    {author}
    - {imports} + {imports} imported
    From 53a148a1b01939cd3bd7cd36e216e78f696e061b Mon Sep 17 00:00:00 2001 From: paulcjy Date: Tue, 3 Dec 2024 20:52:56 +0900 Subject: [PATCH 120/124] =?UTF-8?q?=F0=9F=90=9B=20Design(hub):=20public=20?= =?UTF-8?q?architecure=20board=EC=97=90=20=EC=9B=90=ED=99=94=20=EA=B8=B0?= =?UTF-8?q?=ED=98=B8=20=ED=91=9C=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/hub/src/components/ArchitectureBoard/ArchitectureItem.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/hub/src/components/ArchitectureBoard/ArchitectureItem.tsx b/apps/hub/src/components/ArchitectureBoard/ArchitectureItem.tsx index 2fb3c3ee..8eab1b50 100644 --- a/apps/hub/src/components/ArchitectureBoard/ArchitectureItem.tsx +++ b/apps/hub/src/components/ArchitectureBoard/ArchitectureItem.tsx @@ -38,7 +38,7 @@ export const ArchitectureItem = ({ ))}
    -
    {cost}
    +
    ₩ {cost}
    {stars}
    {imports}
    From ff7c3ab6bb9d3030b600670c8abbe349d84483b1 Mon Sep 17 00:00:00 2001 From: paulcjy Date: Tue, 3 Dec 2024 20:56:41 +0900 Subject: [PATCH 121/124] =?UTF-8?q?=E2=9C=A8=20Feat(hub):=20private=20arch?= =?UTF-8?q?itecture=20board=20=EC=99=84=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PrivateArchitectureBoard/index.tsx | 80 +++++++++---------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/apps/hub/src/components/PrivateArchitectureBoard/index.tsx b/apps/hub/src/components/PrivateArchitectureBoard/index.tsx index d738854b..621d7ee8 100644 --- a/apps/hub/src/components/PrivateArchitectureBoard/index.tsx +++ b/apps/hub/src/components/PrivateArchitectureBoard/index.tsx @@ -7,7 +7,6 @@ import { ErrorMessage } from '@/ui/ErrorMessage'; import { LoadingSpinner } from '@/ui/LoadingSpinner'; import { calculateTotalPages } from '@/utils/pagination'; import Link from 'next/link'; -import { Suspense } from 'react'; export const PrivateArchitectureBoard = ({ apiUrl }: { apiUrl: string }) => { const { params, setParams } = useQueryParams(); @@ -18,47 +17,48 @@ export const PrivateArchitectureBoard = ({ apiUrl }: { apiUrl: string }) => { const handlePageChange = (page: number) => setParams({ page }); - if (error) - return ( - - - - ); + if (error) return ; return ( - -
    - - {/* 헤더 추가하고 아이템 너비 맞추기 */} - {isLoading ? ( - - ) : ( - <> -
    - {data.map((item: any) => ( -
  • - -

    - {item.title} -

    - -
    {item.cost}
    -
    {item.createdAt}
    -
    {item.updatedAt}
    -
  • - ))} -
    - - - )} +
    + +
    + Architecture
    - + {isLoading ? ( + + ) : ( + <> +
    + {data.map((item: any) => ( +
  • + +

    {item.title}

    +
    + 마지막 수정 + + {new Date( + item.updatedAt, + ).toLocaleString()} + +
    + +
  • + ))} +
    + + + )} +
    ); }; From 836cc71835df678e9a77f700f81401b22583d19e Mon Sep 17 00:00:00 2001 From: paulcjy Date: Tue, 3 Dec 2024 21:05:08 +0900 Subject: [PATCH 122/124] =?UTF-8?q?=F0=9F=A4=96=20Refactor(hub):=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/hub/src/app/architectures/[id]/page.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/apps/hub/src/app/architectures/[id]/page.tsx b/apps/hub/src/app/architectures/[id]/page.tsx index 6c98ca88..ebeaa62d 100644 --- a/apps/hub/src/app/architectures/[id]/page.tsx +++ b/apps/hub/src/app/architectures/[id]/page.tsx @@ -8,7 +8,6 @@ import { StarIcon } from '@/ui/StarIcon'; import { Tag } from '@/ui/Tag'; import { fetcher } from '@/utils/fetcher'; import { useParams } from 'next/navigation'; -import { useState } from 'react'; import useSWR from 'swr'; interface PublicArchitecture { @@ -35,10 +34,7 @@ export default function ArchitectureDetailPage() { ); if (isLoading) return ; - if (error) { - // return
    {JSON.stringify(error)}
    ; - return ; - } + if (error) return ; const { title, From 8b5274d25e378ad77ac4b042d5ccebb3ff25ab85 Mon Sep 17 00:00:00 2001 From: p1n9d3v Date: Tue, 3 Dec 2024 22:57:19 +0900 Subject: [PATCH 123/124] =?UTF-8?q?=F0=9F=A4=96=20Refactor(client):=20cons?= =?UTF-8?q?ole.log=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/client/src/apis/loaders.ts | 2 -- apps/client/src/models/ncloud/constants.ts | 6 +++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/apps/client/src/apis/loaders.ts b/apps/client/src/apis/loaders.ts index b4437396..aa3ab4b1 100644 --- a/apps/client/src/apis/loaders.ts +++ b/apps/client/src/apis/loaders.ts @@ -35,8 +35,6 @@ export const rootLoader = async ({ params }: LoaderFunctionArgs) => { let data; try { data = JSON.parse(text, undefinedReviver); - console.log(text); - console.log(data); } catch (error) { throw new Response('Invalid JSON response', { status: 500 }); } diff --git a/apps/client/src/models/ncloud/constants.ts b/apps/client/src/models/ncloud/constants.ts index acacf85e..91e4f0a0 100644 --- a/apps/client/src/models/ncloud/constants.ts +++ b/apps/client/src/models/ncloud/constants.ts @@ -51,9 +51,9 @@ export const SERVER_IMAGE_SPEC_CODE: { ], }; -const krRegionId = `kr-${nanoid()}`; -const jpRegionId = `jp-${nanoid()}`; -const sgRegionId = `sg-${nanoid()}`; +const krRegionId = `kr-region-unique`; +const jpRegionId = `jp-region-unique`; +const sgRegionId = `sg-region-unique`; export const REGIONS: { [key: string]: any } = { KR: { id: krRegionId, From 07f2e69ca20988d79e1034da41e3ab23e971233e Mon Sep 17 00:00:00 2001 From: p1n9d3v Date: Tue, 3 Dec 2024 22:59:50 +0900 Subject: [PATCH 124/124] =?UTF-8?q?=F0=9F=A4=96=20Refactor(client):=20tag?= =?UTF-8?q?=20attribute=20=EC=97=90=EB=9F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/client/src/components/Node/ncloud/ContainerRegistry.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/components/Node/ncloud/ContainerRegistry.tsx b/apps/client/src/components/Node/ncloud/ContainerRegistry.tsx index c963b293..30cfb8de 100644 --- a/apps/client/src/components/Node/ncloud/ContainerRegistry.tsx +++ b/apps/client/src/components/Node/ncloud/ContainerRegistry.tsx @@ -58,7 +58,7 @@ const Node3D = ({ properties }: Props) => {