From 83802dbe090cdf35900d976d5e9f61c21c29b633 Mon Sep 17 00:00:00 2001 From: xudaotutou <13435638964@163.com> Date: Fri, 8 Dec 2023 15:35:58 +0800 Subject: [PATCH 1/2] feat(desktop): prisma refactor(desktop): use CRDB replace mongoDB feat(desktop):store avatar via object storage feat(desktop):proxy oauth2.0 fix(costcenter): fix costcneter for multi-region feat(deskotp): add regionToggle --- frontend/desktop/.gitignore | 6 +- frontend/desktop/jest.config.mjs | 11 +- frontend/desktop/next.config.js | 14 + frontend/desktop/package.json | 37 +- .../migrations/20240229065500_/migration.sql | 154 ++ .../20240301085154_removetabe/migration.sql | 12 + .../global/migrations/migration_lock.toml | 3 + frontend/desktop/prisma/global/schema.prisma | 110 ++ .../20240204080217_init/migration.sql | 57 + .../region/migrations/migration_lock.toml | 3 + frontend/desktop/prisma/region/schema.prisma | 58 + .../public/locales/en/cloudProviders.json | 11 + .../desktop/public/locales/en/common.json | 8 +- .../public/locales/zh/cloudProviders.json | 11 + .../desktop/public/locales/zh/common.json | 10 +- frontend/desktop/scripts/createUser.ps1 | 28 + frontend/desktop/scripts/deployglobal.ps1 | 3 + frontend/desktop/scripts/deployregion.ps1 | 3 + frontend/desktop/scripts/getRegionSecret.ps1 | 3 + frontend/desktop/scripts/init.sql | 11 + .../__tests__/api/e2e/auth/password.test.ts | 46 +- .../api/e2e/namespace/abdicate.test.ts | 89 +- .../api/e2e/namespace/create.test.ts | 57 +- .../api/e2e/namespace/delete.test.ts | 56 +- .../api/e2e/namespace/invite.test.ts | 115 +- .../api/e2e/namespace/modify.test.ts | 104 +- .../api/e2e/namespace/recive.test.ts | 58 +- .../api/e2e/namespace/remove.test.ts | 94 +- .../api/e2e/namespace/switch.test.ts | 84 +- .../api/e2e/namespace/withoutSession.test.ts | 11 +- .../__tests__/api/e2e/v1alpha/signup.test.ts | 20 +- frontend/desktop/src/__tests__/api/tools.ts | 59 +- .../unit/backend/persistImage.test.ts | 29 + frontend/desktop/src/api/auth.ts | 67 +- frontend/desktop/src/api/namespace.ts | 11 +- frontend/desktop/src/api/platform.ts | 2 +- .../desktop/src/components/account/index.tsx | 219 +-- .../src/components/desktop_content/time.tsx | 6 +- .../src/components/region/RegionToggle.tsx | 144 ++ .../src/components/signin/auth/AuthList.tsx | 132 ++ .../components/signin/auth/useAuthList.tsx | 99 -- .../components/signin/auth/useLanguage.tsx | 3 +- .../components/signin/auth/usePassword.tsx | 67 +- .../src/components/signin/auth/useSms.tsx | 58 +- .../src/components/signin/auth/useWechat.tsx | 113 +- .../desktop/src/components/signin/index.tsx | 425 ++--- .../src/components/team/Abdication.tsx | 16 +- .../src/components/team/CreateTeam.tsx | 4 +- .../src/components/team/DissolveTeam.tsx | 2 +- .../src/components/team/InviteMember.tsx | 6 +- .../src/components/team/ModifyRole.tsx | 9 +- .../src/components/team/RemoveMember.tsx | 10 +- .../src/components/team/TeamCenter.tsx | 32 +- .../desktop/src/components/team/userTable.tsx | 19 +- .../src/components/user_menu/index.tsx | 192 +-- frontend/desktop/src/hooks/useDriver.tsx | 9 +- .../src/pages/api/account/getAccount.ts | 16 +- .../src/pages/api/account/getAmount.ts | 56 +- .../src/pages/api/account/payment/pay.ts | 39 - .../src/pages/api/account/updateGuide.ts | 6 +- .../desktop/src/pages/api/auth/canProxy.ts | 45 + .../src/pages/api/auth/getKubeconfig.ts | 31 + .../desktop/src/pages/api/auth/globalToken.ts | 31 + frontend/desktop/src/pages/api/auth/info.ts | 46 + .../src/pages/api/auth/namespace/abdicate.ts | 124 +- .../src/pages/api/auth/namespace/create.ts | 77 +- .../src/pages/api/auth/namespace/delete.ts | 79 +- .../src/pages/api/auth/namespace/details.ts | 79 +- .../src/pages/api/auth/namespace/invite.ts | 84 +- .../src/pages/api/auth/namespace/list.ts | 49 +- .../pages/api/auth/namespace/modifyRole.ts | 78 +- .../src/pages/api/auth/namespace/recive.ts | 49 +- .../pages/api/auth/namespace/removeUser.ts | 101 +- .../src/pages/api/auth/namespace/switch.ts | 76 +- .../pages/api/auth/namespace/verifyInvite.ts | 54 +- .../src/pages/api/auth/oauth/github/index.ts | 51 +- .../src/pages/api/auth/oauth/google/index.ts | 33 +- .../src/pages/api/auth/oauth/wechat/index.ts | 36 +- .../src/pages/api/auth/password/exist.ts | 23 +- .../src/pages/api/auth/password/index.ts | 27 +- .../src/pages/api/auth/password/modify.ts | 57 +- .../src/pages/api/auth/phone/verify.ts | 11 +- .../api/auth/publicWechat/getWechatResult.ts | 22 +- .../desktop/src/pages/api/auth/regionList.ts | 33 + .../desktop/src/pages/api/auth/regionToken.ts | 27 + .../src/pages/api/desktop/getInstalledApps.ts | 18 +- frontend/desktop/src/pages/api/dev/migrate.ts | 550 +++++++ .../src/pages/api/dev/removeDuplicates.ts | 145 ++ .../src/pages/api/notification/global.ts | 4 +- .../src/pages/api/notification/list.ts | 19 +- .../src/pages/api/notification/read.ts | 18 +- .../desktop/src/pages/api/platform/getEnv.ts | 13 +- frontend/desktop/src/pages/api/price/bonus.ts | 28 - frontend/desktop/src/pages/api/price/index.ts | 52 - .../src/pages/api/v1alpha/password/signin.ts | 50 +- .../src/pages/api/v1alpha/password/signup.ts | 64 +- frontend/desktop/src/pages/callback.tsx | 102 +- frontend/desktop/src/pages/index.tsx | 3 +- frontend/desktop/src/pages/proxyOAuth.tsx | 95 ++ frontend/desktop/src/pages/signin.tsx | 1 - frontend/desktop/src/pages/switchRegion.tsx | 76 + frontend/desktop/src/services/backend/auth.ts | 69 +- .../desktop/src/services/backend/db/init.ts | 4 + .../src/services/backend/db/license.ts | 72 - .../src/services/backend/db/namespace.ts | 81 - .../desktop/src/services/backend/db/user.ts | 215 --- .../services/backend/db/userToNamespace.ts | 267 +--- .../src/services/backend/globalAuth.ts | 243 +++ .../src/services/backend/kubernetes/user.ts | 8 +- .../desktop/src/services/backend/oauth.ts | 253 --- .../src/services/backend/persistImage.ts | 51 + .../src/services/backend/regionAuth.ts | 168 ++ frontend/desktop/src/services/backend/team.ts | 150 +- frontend/desktop/src/services/enable.ts | 10 + frontend/desktop/src/services/request.ts | 8 +- frontend/desktop/src/stores/global.ts | 1 + frontend/desktop/src/stores/session.ts | 33 +- frontend/desktop/src/types/index.ts | 2 +- frontend/desktop/src/types/oauth.ts | 43 + frontend/desktop/src/types/region.ts | 19 + frontend/desktop/src/types/session.ts | 3 + frontend/desktop/src/types/system.ts | 2 +- frontend/desktop/src/types/token.ts | 11 + frontend/desktop/src/types/user.ts | 5 +- frontend/desktop/src/utils/tools.ts | 25 +- frontend/desktop/tsconfig.json | 14 +- .../ui/src/components/icons/ChangeIcon.tsx | 26 + .../ui/src/components/icons/ProviderIcon.tsx | 8 + frontend/packages/ui/src/components/index.ts | 6 +- frontend/packages/ui/src/theme/colors.ts | 7 +- frontend/pnpm-lock.yaml | 1375 ++++++++++++----- .../costcenter/public/locales/en/common.json | 2 +- .../costcenter/public/locales/zh/common.json | 1 - .../components => }/RechargeModal.tsx | 0 .../components => }/TransferModal.tsx | 0 .../src/components/billing/AmountDisplay.tsx | 53 + .../components/billing/AmountTableHeader.tsx | 21 + .../src/components/billing/InOutTabPanel.tsx | 106 ++ .../components/billing/RechargeTabPanel.tsx | 150 ++ .../src/components/billing/SearchBox.tsx | 2 +- .../src/components/billing/SwitchPage.tsx | 2 +- .../components/billing/TransferTabPnel.tsx | 113 ++ .../src/components/billing/billingTable.tsx | 43 +- .../cost_overview/components/lineChart.tsx | 3 +- .../cost_overview/components/user.tsx | 5 +- .../costcenter/src/hooks/useBillingData.tsx | 2 +- .../src/pages/api/account/getAmount.ts | 51 +- .../src/pages/api/account/transfer.ts | 1 + .../billing/{getAppList.tsx => getAppList.ts} | 0 ...tNamespaceList.tsx => getNamespaceList.ts} | 0 .../src/pages/api/billing/recharge.ts | 60 + .../costcenter/src/pages/api/price/bonus.ts | 30 +- .../costcenter/src/pages/billing/index.tsx | 274 +--- .../providers/costcenter/src/test/testbot.ps1 | 28 - .../providers/costcenter/src/types/billing.ts | 11 + 155 files changed, 6230 insertions(+), 3605 deletions(-) create mode 100644 frontend/desktop/prisma/global/migrations/20240229065500_/migration.sql create mode 100644 frontend/desktop/prisma/global/migrations/20240301085154_removetabe/migration.sql create mode 100644 frontend/desktop/prisma/global/migrations/migration_lock.toml create mode 100644 frontend/desktop/prisma/global/schema.prisma create mode 100644 frontend/desktop/prisma/region/migrations/20240204080217_init/migration.sql create mode 100644 frontend/desktop/prisma/region/migrations/migration_lock.toml create mode 100644 frontend/desktop/prisma/region/schema.prisma create mode 100644 frontend/desktop/public/locales/en/cloudProviders.json create mode 100644 frontend/desktop/public/locales/zh/cloudProviders.json create mode 100644 frontend/desktop/scripts/createUser.ps1 create mode 100644 frontend/desktop/scripts/deployglobal.ps1 create mode 100644 frontend/desktop/scripts/deployregion.ps1 create mode 100644 frontend/desktop/scripts/getRegionSecret.ps1 create mode 100644 frontend/desktop/scripts/init.sql create mode 100644 frontend/desktop/src/__tests__/unit/backend/persistImage.test.ts create mode 100644 frontend/desktop/src/components/region/RegionToggle.tsx create mode 100644 frontend/desktop/src/components/signin/auth/AuthList.tsx delete mode 100644 frontend/desktop/src/components/signin/auth/useAuthList.tsx delete mode 100644 frontend/desktop/src/pages/api/account/payment/pay.ts create mode 100644 frontend/desktop/src/pages/api/auth/canProxy.ts create mode 100644 frontend/desktop/src/pages/api/auth/getKubeconfig.ts create mode 100644 frontend/desktop/src/pages/api/auth/globalToken.ts create mode 100644 frontend/desktop/src/pages/api/auth/info.ts create mode 100644 frontend/desktop/src/pages/api/auth/regionList.ts create mode 100644 frontend/desktop/src/pages/api/auth/regionToken.ts create mode 100644 frontend/desktop/src/pages/api/dev/migrate.ts create mode 100644 frontend/desktop/src/pages/api/dev/removeDuplicates.ts delete mode 100644 frontend/desktop/src/pages/api/price/bonus.ts delete mode 100644 frontend/desktop/src/pages/api/price/index.ts create mode 100644 frontend/desktop/src/pages/proxyOAuth.tsx create mode 100644 frontend/desktop/src/pages/switchRegion.tsx create mode 100644 frontend/desktop/src/services/backend/db/init.ts delete mode 100644 frontend/desktop/src/services/backend/db/license.ts delete mode 100644 frontend/desktop/src/services/backend/db/namespace.ts delete mode 100644 frontend/desktop/src/services/backend/db/user.ts create mode 100644 frontend/desktop/src/services/backend/globalAuth.ts delete mode 100644 frontend/desktop/src/services/backend/oauth.ts create mode 100644 frontend/desktop/src/services/backend/persistImage.ts create mode 100644 frontend/desktop/src/services/backend/regionAuth.ts create mode 100644 frontend/desktop/src/types/oauth.ts create mode 100644 frontend/desktop/src/types/region.ts create mode 100644 frontend/desktop/src/types/token.ts create mode 100644 frontend/packages/ui/src/components/icons/ChangeIcon.tsx create mode 100644 frontend/packages/ui/src/components/icons/ProviderIcon.tsx rename frontend/providers/costcenter/src/components/{cost_overview/components => }/RechargeModal.tsx (100%) rename frontend/providers/costcenter/src/components/{cost_overview/components => }/TransferModal.tsx (100%) create mode 100644 frontend/providers/costcenter/src/components/billing/AmountDisplay.tsx create mode 100644 frontend/providers/costcenter/src/components/billing/AmountTableHeader.tsx create mode 100644 frontend/providers/costcenter/src/components/billing/InOutTabPanel.tsx create mode 100644 frontend/providers/costcenter/src/components/billing/RechargeTabPanel.tsx create mode 100644 frontend/providers/costcenter/src/components/billing/TransferTabPnel.tsx rename frontend/providers/costcenter/src/pages/api/billing/{getAppList.tsx => getAppList.ts} (100%) rename frontend/providers/costcenter/src/pages/api/billing/{getNamespaceList.tsx => getNamespaceList.ts} (100%) create mode 100644 frontend/providers/costcenter/src/pages/api/billing/recharge.ts delete mode 100644 frontend/providers/costcenter/src/test/testbot.ps1 diff --git a/frontend/desktop/.gitignore b/frontend/desktop/.gitignore index a09c663e148..9e88043fc96 100644 --- a/frontend/desktop/.gitignore +++ b/frontend/desktop/.gitignore @@ -11,7 +11,7 @@ # next.js /.next/ /out/ - +/prisma/**/generated # production /build @@ -40,4 +40,6 @@ tests/* .yalc/ yalc.lock -config.yaml \ No newline at end of file +config.yaml +.env +#/prisma/region/generated/ diff --git a/frontend/desktop/jest.config.mjs b/frontend/desktop/jest.config.mjs index 345119c6f65..e878d9804dc 100644 --- a/frontend/desktop/jest.config.mjs +++ b/frontend/desktop/jest.config.mjs @@ -13,12 +13,15 @@ const config = { // Add more setup options before each test is run // setupFilesAfterEnv: ['/jest.setup.js'], moduleNameMapper: { - '^@/(.*)$': '/src/$1' + '^@/(.*)$': '/src/$1', + "^nanoid(/(.*)|$)": "nanoid$1" }, testSequencer:'./src/__tests__/jest-sequencer.cjs', - testMatch: ['**/__tests__/**/*.test.ts'], - testEnvironment:'node', - maxWorkers: 1 + testMatch: ['**/__tests__/**/*.test.ts'], + maxWorkers: 1, + "transformIgnorePatterns": [ + "/node_modules/(?!(nanoid)/)" + ] } // createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async diff --git a/frontend/desktop/next.config.js b/frontend/desktop/next.config.js index 9bd4c668188..3755c847472 100644 --- a/frontend/desktop/next.config.js +++ b/frontend/desktop/next.config.js @@ -3,6 +3,7 @@ const path = require('path'); const runtimeCaching = require('next-pwa/cache'); const isProduction = process.env.NODE_ENV === 'production'; const { i18n } = require('./next-i18next.config'); + const withPWA = require('next-pwa')({ dest: 'public', runtimeCaching, @@ -12,6 +13,19 @@ const withPWA = require('next-pwa')({ const nextConfig = withPWA({ i18n, reactStrictMode: false, + async redirects() { + if (isProduction) { + return [ + { + source: '/api/dev/:slug', + destination: '/', + permanent: true + } + ]; + } else { + return []; + } + }, swcMinify: isProduction, output: 'standalone', transpilePackages: ['@sealos/ui', 'sealos-desktop-sdk', '@sealos/driver'], diff --git a/frontend/desktop/package.json b/frontend/desktop/package.json index 94ffa0f7835..85c84ebc12d 100644 --- a/frontend/desktop/package.json +++ b/frontend/desktop/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "private": true, "scripts": { - "dev": "next dev", + "dev": "dotenv -e .env.local next dev", "build": "next build", "start": "next start", "lint": "next lint", @@ -11,7 +11,10 @@ "test:e2e-namespace": "jest --testPathPattern=/e2e/namespace/ --runInBand", "test:e2e-auth": "jest --testPathPattern=/e2e/auth/ --runInBand", "test:e2e-api": "jest --testPathPattern=/e2e/v1alpha/ --runInBand", - "test:ci": "jest --runInBand" + "test:ci": "jest --runInBand", + "gen:global": "prisma generate --schema ./prisma/global/schema.prisma", + "gen:region": "prisma generate --schema ./prisma/region/schema.prisma", + "postinstall": "pnpm gen:global && pnpm gen:region" }, "dependencies": { "@alicloud/dysmsapi20170525": "^2.0.24", @@ -25,12 +28,13 @@ "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", "@kubernetes/client-node": "^0.18.1", + "@prisma/client": "^5.10.2", "@sealos/driver": "workspace:^", "@sealos/ui": "workspace:^", "@tanstack/react-query": "^4.35.3", "axios": "^1.5.1", "clsx": "^1.2.1", - "cors": "^2.8.5", + "cors": "^2.8.5", "dayjs": "^1.11.10", "eslint": "8.38.0", "eslint-config-next": "13.3.0", @@ -40,7 +44,9 @@ "js-cookie": "^3.0.5", "js-yaml": "^4.1.0", "jsonwebtoken": "^9.0.2", + "jwt-decode": "^4.0.0", "lodash": "^4.17.21", + "minio": "^7.1.3", "mongodb": "^5.9.0", "nanoid": "^4.0.2", "next": "13.3.0", @@ -56,6 +62,7 @@ "react-i18next": "^12.3.1", "sass": "^1.68.0", "sealos-desktop-sdk": "workspace:*", + "sharp": "^0.32.6", "uuid": "^9.0.1", "xml2js": "^0.6.2", "zustand": "^4.4.1" @@ -63,18 +70,38 @@ "devDependencies": { "@testing-library/jest-dom": "^6.1.3", "@testing-library/react": "^14.0.0", - "@types/jest": "^29.5.5", + "@types/jest": "^29.5.10", "@types/js-cookie": "^3.0.4", "@types/js-yaml": "^4.0.6", "@types/jsonwebtoken": "^9.0.3", "@types/lodash": "^4.14.199", + "@types/minio": "^7.1.1", "@types/node": "18.15.11", "@types/nprogress": "^0.2.1", "@types/react": "18.2.37", "@types/react-dom": "18.0.11", "@types/uuid": "^9.0.4", + "dotenv-cli": "^7.3.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", - "prettier": "^2.8.8" + "prettier": "^2.8.8", + "prisma": "^5.10.2" + }, + "pnpm": { + "supportedArchitectures": { + "os": [ + "win32", + "darwin", + "current", + "linuxmusl" + ], + "cpu": [ + "x64", + "arm64" + ], + "libc": [ + "musl" + ] + } } } \ No newline at end of file diff --git a/frontend/desktop/prisma/global/migrations/20240229065500_/migration.sql b/frontend/desktop/prisma/global/migrations/20240229065500_/migration.sql new file mode 100644 index 00000000000..2bf31ebe141 --- /dev/null +++ b/frontend/desktop/prisma/global/migrations/20240229065500_/migration.sql @@ -0,0 +1,154 @@ +-- CreateEnum +CREATE TYPE "ProviderType" AS ENUM ('PHONE', 'GITHUB', 'WECHAT', 'GOOGLE', 'PASSWORD'); + +-- CreateTable +CREATE TABLE "OauthProvider" ( + "uid" UUID NOT NULL DEFAULT gen_random_uuid(), + "userUid" UUID NOT NULL, + "createdAt" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMPTZ(3) NOT NULL, + "providerType" "ProviderType" NOT NULL, + "providerId" STRING NOT NULL, + "password" STRING, + + CONSTRAINT "OauthProvider_pkey" PRIMARY KEY ("uid") +); + +-- CreateTable +CREATE TABLE "Region" ( + "uid" UUID NOT NULL DEFAULT gen_random_uuid(), + "displayName" STRING NOT NULL, + "location" STRING NOT NULL, + "domain" STRING NOT NULL, + "description" STRING, + + CONSTRAINT "Region_pkey" PRIMARY KEY ("uid") +); + +-- CreateTable +CREATE TABLE "Account" ( + "userUid" UUID NOT NULL DEFAULT gen_random_uuid(), + "activityBonus" INT8 NOT NULL, + "encryptBalance" STRING NOT NULL, + "encryptDeductionBalance" STRING NOT NULL, + "created_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "create_region_id" STRING NOT NULL, + "balance" INT8, + "deduction_balance" INT8, + + CONSTRAINT "Account_pkey" PRIMARY KEY ("userUid") +); + +-- CreateTable +CREATE TABLE "ErrorPaymentCreate" ( + "userUid" UUID NOT NULL, + "regionUid" UUID NOT NULL, + "created_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "regionUserOwner" STRING NOT NULL, + "method" STRING NOT NULL, + "amount" INT8 NOT NULL, + "gift" INT8, + "trade_no" STRING NOT NULL, + "code_url" STRING, + "invoiced_at" BOOL DEFAULT false, + "remark" STRING, + "message" STRING NOT NULL, + "create_time" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +-- CreateTable +CREATE TABLE "Payment" ( + "id" STRING NOT NULL, + "userUid" UUID NOT NULL, + "regionUid" UUID NOT NULL, + "created_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "regionUserOwner" STRING NOT NULL, + "method" STRING NOT NULL, + "amount" INT8 NOT NULL, + "gift" INT8, + "trade_no" STRING NOT NULL, + "code_url" STRING, + "invoiced_at" BOOL DEFAULT false, + "remark" STRING, + "message" STRING NOT NULL, + + CONSTRAINT "Payment_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "TransferAccountV1" ( + "regionUid" UUID NOT NULL, + "regionUserOwner" STRING NOT NULL, + "userUid" UUID NOT NULL DEFAULT gen_random_uuid(), + "activityBonus" INT8 NOT NULL, + "encryptBalance" STRING NOT NULL, + "encryptDeductionBalance" STRING NOT NULL, + "created_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "create_region_id" STRING NOT NULL, + "balance" INT8, + "deduction_balance" INT8, + + CONSTRAINT "TransferAccountV1_pkey" PRIMARY KEY ("userUid") +); + +-- CreateTable +CREATE TABLE "User" ( + "uid" UUID NOT NULL DEFAULT gen_random_uuid(), + "createdAt" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMPTZ(3) NOT NULL, + "avatarUri" STRING NOT NULL, + "nickname" STRING NOT NULL, + "id" STRING NOT NULL, + "name" STRING NOT NULL, + + CONSTRAINT "User_pkey" PRIMARY KEY ("uid") +); + +-- CreateTable +CREATE TABLE "ErrorAccountCreate" ( + "userUid" UUID NOT NULL DEFAULT gen_random_uuid(), + "activityBonus" INT8 NOT NULL, + "encryptBalance" STRING NOT NULL, + "encryptDeductionBalance" STRING NOT NULL, + "created_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "create_region_id" STRING NOT NULL, + "balance" INT8, + "deduction_balance" INT8, + "userCr" STRING NOT NULL, + "error_time" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "regionUid" UUID NOT NULL, + "regionUserOwner" STRING NOT NULL, + "message" STRING NOT NULL, + + CONSTRAINT "ErrorAccountCreate_pkey" PRIMARY KEY ("userUid") +); + +-- CreateTable +CREATE TABLE "NullUserRecord" ( + "crName" STRING NOT NULL, + "region_id" STRING NOT NULL +); + +-- CreateIndex +CREATE INDEX "OauthProvider_userUid_idx" ON "OauthProvider"("userUid"); + +-- CreateIndex +CREATE UNIQUE INDEX "OauthProvider_providerId_providerType_key" ON "OauthProvider"("providerId", "providerType"); + +-- CreateIndex +CREATE UNIQUE INDEX "ErrorPaymentCreate_trade_no_key" ON "ErrorPaymentCreate"("trade_no"); + +-- CreateIndex +CREATE UNIQUE INDEX "Payment_trade_no_key" ON "Payment"("trade_no"); + +-- CreateIndex +CREATE UNIQUE INDEX "User_id_key" ON "User"("id"); + +-- CreateIndex +CREATE UNIQUE INDEX "User_name_key" ON "User"("name"); + +-- CreateIndex +CREATE UNIQUE INDEX "ErrorAccountCreate_userCr_key" ON "ErrorAccountCreate"("userCr"); + +-- CreateIndex +CREATE UNIQUE INDEX "NullUserRecord_crName_key" ON "NullUserRecord"("crName"); diff --git a/frontend/desktop/prisma/global/migrations/20240301085154_removetabe/migration.sql b/frontend/desktop/prisma/global/migrations/20240301085154_removetabe/migration.sql new file mode 100644 index 00000000000..65aa00b6d53 --- /dev/null +++ b/frontend/desktop/prisma/global/migrations/20240301085154_removetabe/migration.sql @@ -0,0 +1,12 @@ +/* + Warnings: + + - You are about to drop the `NullUserRecord` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `TransferAccountV1` table. If the table is not empty, all the data it contains will be lost. + +*/ +-- DropTable +DROP TABLE "NullUserRecord"; + +-- DropTable +DROP TABLE "TransferAccountV1"; diff --git a/frontend/desktop/prisma/global/migrations/migration_lock.toml b/frontend/desktop/prisma/global/migrations/migration_lock.toml new file mode 100644 index 00000000000..7106c8a8488 --- /dev/null +++ b/frontend/desktop/prisma/global/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 = "cockroachdb" \ No newline at end of file diff --git a/frontend/desktop/prisma/global/schema.prisma b/frontend/desktop/prisma/global/schema.prisma new file mode 100644 index 00000000000..579aff516ea --- /dev/null +++ b/frontend/desktop/prisma/global/schema.prisma @@ -0,0 +1,110 @@ +generator globalClient { + provider = "prisma-client-js" + output = "./generated/client" +} + +datasource db { + provider = "cockroachdb" + url = env("GLOBAL_DATABASE_URL") + relationMode = "prisma" +} + +model OauthProvider { + uid String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid + userUid String @db.Uuid + createdAt DateTime @default(now()) @db.Timestamptz(3) + updatedAt DateTime @updatedAt @db.Timestamptz(3) + providerType ProviderType + providerId String + password String? + user User @relation(fields: [userUid], references: [uid]) + + @@unique([providerId, providerType]) + @@index([userUid]) +} + +model Region { + uid String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid + displayName String + location String + domain String + description String? +} + +model Account { + userUid String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid + activityBonus BigInt + encryptBalance String + encryptDeductionBalance String + created_at DateTime @default(now()) @db.Timestamptz(3) + create_region_id String + balance BigInt? + deduction_balance BigInt? +} + +model ErrorPaymentCreate { + userUid String @db.Uuid + regionUid String @db.Uuid + created_at DateTime @default(now()) @db.Timestamptz(3) + regionUserOwner String + method String + amount BigInt + gift BigInt? + trade_no String @unique + code_url String? + invoiced_at Boolean? @default(false) + remark String? + message String + create_time DateTime @default(now()) @db.Timestamptz(3) +} + +model Payment { + id String @id + userUid String @db.Uuid + regionUid String @db.Uuid + created_at DateTime @default(now()) @db.Timestamptz(3) + regionUserOwner String + method String + amount BigInt + gift BigInt? + trade_no String @unique + code_url String? + invoiced_at Boolean? @default(false) + remark String? + message String +} + +model User { + uid String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid + createdAt DateTime @default(now()) @db.Timestamptz(3) + updatedAt DateTime @updatedAt @db.Timestamptz(3) + avatarUri String + nickname String + id String @unique + name String @unique + oauthProvider OauthProvider[] +} + +model ErrorAccountCreate { + userUid String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid + activityBonus BigInt + encryptBalance String + encryptDeductionBalance String + created_at DateTime @default(now()) @db.Timestamptz(3) + create_region_id String + balance BigInt? + deduction_balance BigInt? + userCr String @unique + error_time DateTime @default(now()) @db.Timestamptz(3) + regionUid String @db.Uuid + regionUserOwner String + message String +} + +enum ProviderType { + PHONE + GITHUB + WECHAT + GOOGLE + PASSWORD +} diff --git a/frontend/desktop/prisma/region/migrations/20240204080217_init/migration.sql b/frontend/desktop/prisma/region/migrations/20240204080217_init/migration.sql new file mode 100644 index 00000000000..5fc288a5236 --- /dev/null +++ b/frontend/desktop/prisma/region/migrations/20240204080217_init/migration.sql @@ -0,0 +1,57 @@ +-- CreateEnum +CREATE TYPE "JoinStatus" AS ENUM ('INVITED', 'IN_WORKSPACE', 'NOT_IN_WORKSPACE'); + +-- CreateEnum +CREATE TYPE "Role" AS ENUM ('MANAGER', 'DEVELOPER', 'OWNER'); + +-- CreateTable +CREATE TABLE "Workspace" ( + "uid" UUID NOT NULL DEFAULT gen_random_uuid(), + "id" STRING NOT NULL, + "displayName" STRING NOT NULL, + "createdAt" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMPTZ(3) NOT NULL, + + CONSTRAINT "Workspace_pkey" PRIMARY KEY ("uid") +); + +-- CreateTable +CREATE TABLE "UserCr" ( + "uid" UUID NOT NULL DEFAULT gen_random_uuid(), + "crName" STRING NOT NULL, + "userUid" UUID NOT NULL, + "createdAt" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMPTZ(3) NOT NULL, + CONSTRAINT "UserCr_pkey" PRIMARY KEY ("uid") +); + +-- CreateTable +CREATE TABLE "UserWorkspace" ( + "uid" UUID NOT NULL DEFAULT gen_random_uuid(), + "createdAt" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMPTZ(3) NOT NULL, + "workspaceUid" UUID NOT NULL, + "userCrUid" UUID NOT NULL, + "handlerUid" UUID, + "role" "Role" NOT NULL DEFAULT 'DEVELOPER', + "status" "JoinStatus" NOT NULL, + "isPrivate" BOOL NOT NULL, + "joinAt" TIMESTAMPTZ(3), + + CONSTRAINT "UserWorkspace_pkey" PRIMARY KEY ("uid") +); + +-- CreateIndex +CREATE UNIQUE INDEX "UserCr_crName_key" ON "UserCr"("crName"); + +-- CreateIndex +CREATE UNIQUE INDEX "UserCr_userUid_key" ON "UserCr"("userUid"); + +-- CreateIndex +CREATE INDEX "UserWorkspace_userCrUid_idx" ON "UserWorkspace"("userCrUid"); + +-- CreateIndex +CREATE UNIQUE INDEX "UserWorkspace_workspaceUid_userCrUid_key" ON "UserWorkspace"("workspaceUid", "userCrUid"); + +-- CreateIndex +CREATE UNIQUE INDEX "Workspace_id_key" ON "Workspace"("id"); \ No newline at end of file diff --git a/frontend/desktop/prisma/region/migrations/migration_lock.toml b/frontend/desktop/prisma/region/migrations/migration_lock.toml new file mode 100644 index 00000000000..7106c8a8488 --- /dev/null +++ b/frontend/desktop/prisma/region/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 = "cockroachdb" \ No newline at end of file diff --git a/frontend/desktop/prisma/region/schema.prisma b/frontend/desktop/prisma/region/schema.prisma new file mode 100644 index 00000000000..ca03b335563 --- /dev/null +++ b/frontend/desktop/prisma/region/schema.prisma @@ -0,0 +1,58 @@ +generator regionClient { + provider = "prisma-client-js" + output = "./generated/client" +} + +datasource db { + provider = "cockroachdb" + url = env("REGION_DATABASE_URL") + relationMode = "prisma" +} + +model Workspace { + uid String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid + id String @unique + displayName String + createdAt DateTime @default(now()) @db.Timestamptz(3) + updatedAt DateTime @updatedAt @db.Timestamptz(3) + userWorkspace UserWorkspace[] +} + +model UserCr { + uid String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid + crName String @unique + userUid String @unique @db.Uuid + createdAt DateTime @default(now()) @db.Timestamptz(3) + updatedAt DateTime @db.Timestamptz(3) @updatedAt + userWorkspace UserWorkspace[] +} + +model UserWorkspace { + uid String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid + createdAt DateTime @default(now()) @db.Timestamptz(3) + updatedAt DateTime @db.Timestamptz(3) @updatedAt + workspaceUid String @db.Uuid + userCrUid String @db.Uuid + handlerUid String? @db.Uuid + role Role @default(DEVELOPER) + status JoinStatus + isPrivate Boolean + joinAt DateTime? @db.Timestamptz(3) + userCr UserCr @relation(fields: [userCrUid], references: [uid]) + workspace Workspace @relation(fields: [workspaceUid], references: [uid]) + + @@unique([workspaceUid, userCrUid]) + @@index([userCrUid]) +} + +enum JoinStatus { + INVITED + IN_WORKSPACE + NOT_IN_WORKSPACE +} + +enum Role { + MANAGER + DEVELOPER + OWNER +} diff --git a/frontend/desktop/public/locales/en/cloudProviders.json b/frontend/desktop/public/locales/en/cloudProviders.json new file mode 100644 index 00000000000..853501e3b41 --- /dev/null +++ b/frontend/desktop/public/locales/en/cloudProviders.json @@ -0,0 +1,11 @@ +{ + "Volcano Engine": "Volcano Engine", + "Alibaba Cloud": "Alibaba Cloud", + "Tencent Cloud": "Tencent Cloud", + "Google Cloud": "Google Cloud", + "Provider": "Provider", + "Beijing": "Beijing", + "Singapore": "Singapore", + "Guangzhou": "Guangzhou", + "Hangzhou": "Hangzhou" +} diff --git a/frontend/desktop/public/locales/en/common.json b/frontend/desktop/public/locales/en/common.json index 6372e43c429..f50d1b6ed7b 100644 --- a/frontend/desktop/public/locales/en/common.json +++ b/frontend/desktop/public/locales/en/common.json @@ -100,6 +100,7 @@ "Purchase Link Error": "Purchase Link Error", "You have not purchased the License": "You have not purchased the License", "App Info": "App Info", + "Click anywhere to continue": "Click on any blank space to continue", "Jump Over": "Jump Over", "Detail": "Detail", "Hello, welcome": "Hello, welcome to", @@ -118,5 +119,10 @@ "gift amount": "Reward {{amount}} balance.", "Recharge Amount": "Recharge Amount", "Doc": "Doc", - "Official account login": "Official account login" + "Official account login": "Official account login", + "Region": "Region", + "Year": "Year", + "Core": "Core", + "Yuan": "Yuan", + "Description": "Description" } diff --git a/frontend/desktop/public/locales/zh/cloudProviders.json b/frontend/desktop/public/locales/zh/cloudProviders.json new file mode 100644 index 00000000000..e4c2056e48b --- /dev/null +++ b/frontend/desktop/public/locales/zh/cloudProviders.json @@ -0,0 +1,11 @@ +{ + "Volcano Engine": "火山引擎", + "Alibaba Cloud": "阿里云", + "Tencent Cloud": "腾讯云", + "Google Cloud": "谷歌云", + "Provider": "供应商", + "Beijing": "北京", + "Singapore": "新加坡", + "Guangzhou": "广州", + "Hangzhou": "杭州" +} diff --git a/frontend/desktop/public/locales/zh/common.json b/frontend/desktop/public/locales/zh/common.json index 3581118cdc7..dc680a927c1 100644 --- a/frontend/desktop/public/locales/zh/common.json +++ b/frontend/desktop/public/locales/zh/common.json @@ -24,6 +24,8 @@ "confirmNewPassword": "确认新密码", "passwordMismatch": "密码不一致", "passwordChangeSuccess": "密码修改成功", + "passwordMismatch": "密码不一致", + "newPassword": "新密码", "Verify password": "确认密码", "change": "修改", "Invalid username or password": "用户名或密码错误", @@ -94,6 +96,7 @@ "Purchase Link Error": "购买链接错误", "You have not purchased the License": "您还没有购买 License", "App Info": "应用信息", + "Click anywhere to continue": "点击任意空白继续", "Jump Over": "跳过", "Detail": "详情", "Hello, welcome": "您好, 欢迎来到", @@ -111,5 +114,10 @@ "Start your Sealos journey": "开始您的 Sealos 之旅", "gift amount": "赠送 {{amount}} 余额.", "Doc": "文档", - "Official account login": "公众号登录" + "Official account login": "公众号登录", + "Region": "可用区", + "Year": "年", + "Core": "核", + "Yuan": "元", + "Description": "描述" } diff --git a/frontend/desktop/scripts/createUser.ps1 b/frontend/desktop/scripts/createUser.ps1 new file mode 100644 index 00000000000..e8a743b411d --- /dev/null +++ b/frontend/desktop/scripts/createUser.ps1 @@ -0,0 +1,28 @@ +$desktopHostName = Read-Host "输入域名" +$url = "https://${desktopHostName}/api/auth/password" +$tokenUrl = "https://${desktopHostName}/api/auth/regionToken" +Write-Host $url +$requests = 500 +$batchSize = 20 +$delay = 5 + +for ($i = 0; $i -lt $requests; $i += $batchSize) { + + foreach ($num in ($i..($i + $batchSize))) { + $body = @{ + "user" = "test${num}test" + "password" = "test${num}test" + } | ConvertTo-Json + # Start-Job -ScriptBlock { + $result = Invoke-WebRequest -Uri $url -Body $body -ContentType 'application/json' + $token = [URI]::EscapeDataString( ($result.Content | ConvertFrom-Json).data.token) + Write-Host $token + $result2 = Invoke-WebRequest -Uri $tokenUrl -Headers @{ + 'Authorization' = $token + } + Write-Host $result2.Content + # } + } + + # Start-Sleep -Seconds $delay +} diff --git a/frontend/desktop/scripts/deployglobal.ps1 b/frontend/desktop/scripts/deployglobal.ps1 new file mode 100644 index 00000000000..652da2e188b --- /dev/null +++ b/frontend/desktop/scripts/deployglobal.ps1 @@ -0,0 +1,3 @@ +$env:GLOBAL_DATABASE_URL = Read-Host -Prompt "get GLOBAL_DATABASE_URL" + +pnpm.ps1 prisma migrate deploy --schema ./prisma/global/schema.prisma diff --git a/frontend/desktop/scripts/deployregion.ps1 b/frontend/desktop/scripts/deployregion.ps1 new file mode 100644 index 00000000000..8fad50d051c --- /dev/null +++ b/frontend/desktop/scripts/deployregion.ps1 @@ -0,0 +1,3 @@ +$env:REGION_DATABASE_URL = Read-Host -Prompt "get REGION_DATABASE_URL" + +pnpm.ps1 prisma migrate deploy --schema ./prisma/region/schema.prisma diff --git a/frontend/desktop/scripts/getRegionSecret.ps1 b/frontend/desktop/scripts/getRegionSecret.ps1 new file mode 100644 index 00000000000..65f463b117a --- /dev/null +++ b/frontend/desktop/scripts/getRegionSecret.ps1 @@ -0,0 +1,3 @@ +$secret_data = (kubectl get secret desktop-frontend-secret -nsealos -ojson | ConvertFrom-Json).data +Write-Host "JWT_SECRET_REGION=$($secret_data.JWT_SECRET_REGION)" +Write-Host "PASSWORD_SALT=$($secret_data.PASSWORD_SALT)" \ No newline at end of file diff --git a/frontend/desktop/scripts/init.sql b/frontend/desktop/scripts/init.sql new file mode 100644 index 00000000000..9f443df5147 --- /dev/null +++ b/frontend/desktop/scripts/init.sql @@ -0,0 +1,11 @@ +ALTER DATABASE "sealos-desktop" SET PRIMARY REGION "us-east1"; +ALTER DATABASE "sealos-desktop" ADD region "us-west1"; +ALTER DATABASE "sealos-desktop" ADD region "europe-west1"; +ALTER TABLE "RegionUser" SET LOCALITY REGIONAL BY ROW; +ALTER TABLE "RegionUserToWorkspace" SET LOCALITY REGIONAL BY ROW; +ALTER TABLE "Workspace" SET LOCALITY REGIONAL BY ROW; +ALTER TABLE "OauthProvider" SET LOCALITY GLOBAL; +ALTER TABLE "RegionUser" SET LOCALITY GLOBAL; +ALTER TABLE "Region" SET LOCALITY GLOBAL; +insert into "Region" (uid, "displayName", location, "utcDelta", "domain") +values ('b7c94022-6f17-4252-bf24-b937108712a5', 'home', 'us-west1', 8, 'localhost'); diff --git a/frontend/desktop/src/__tests__/api/e2e/auth/password.test.ts b/frontend/desktop/src/__tests__/api/e2e/auth/password.test.ts index 43d801edca1..a245cd3f960 100644 --- a/frontend/desktop/src/__tests__/api/e2e/auth/password.test.ts +++ b/frontend/desktop/src/__tests__/api/e2e/auth/password.test.ts @@ -1,35 +1,33 @@ -import { Session } from 'sealos-desktop-sdk/*'; import * as k8s from '@kubernetes/client-node'; import { _passwordLoginRequest, _passwordModifyRequest } from '@/api/auth'; import { _setAuth, cleanDb, cleanK8s } from '@/__tests__/api/tools'; -import { _createRequest } from '@/api/namespace'; import request from '@/__tests__/api/request'; -import { Db, MongoClient } from 'mongodb'; +import { PrismaClient } from 'prisma/region/generated/client'; + describe('Password', () => { - let session: Session; - const createRequest = _createRequest(request); const modifyRequest = _passwordModifyRequest(request); - let db: Db; - let connection: MongoClient; + console.log('?', process.env.REGION_DATABASE_URL!); + const prisma = new PrismaClient({ + datasourceUrl: process.env.REGION_DATABASE_URL! + }); + // let db; const setAuth = _setAuth(request); + const passwordLoginRequest = _passwordLoginRequest(request, setAuth); beforeAll(async () => { - //@ts-ignore - const uri = process.env.MONGODB_URI as string; - // console.log('MONGODB_URI', uri) - connection = new MongoClient(uri); - await connection.connect(); - db = connection.db(); + // await prisma.$connect(); const kc = new k8s.KubeConfig(); - await cleanK8s(kc, db); - await cleanDb(db); - const res = await _passwordLoginRequest(request)({ user: 'createTest', password: 'testtest' }); - expect(res.data?.user).toBeDefined(); - session = res.data!; - setAuth(session); - console.log('create,', session.user); + console.log('clean start!'); + await cleanK8s(kc, prisma); + await cleanDb(prisma); + console.log('clean end'); + const res = await passwordLoginRequest({ user: 'createTest', password: 'testtest' }); + const token = res?.data?.token!; + expect(token).toBeDefined(); + setAuth(token); + console.log('create,', token); }, 100000); afterAll(async () => { - await connection.close(); + // await prisma.$disconnect(); }); it('modify: null param', async () => { // @ts-ignore @@ -53,15 +51,15 @@ describe('Password', () => { it('same password', async () => { const res = await modifyRequest({ oldPassword: 'testtest', newPassword: 'testtest' }); - expect(res.code).toBe(409); + expect(res.code).toBe(200); }); it('right password', async () => { const res = await modifyRequest({ oldPassword: 'testtest', newPassword: 'testtest2' }); expect(res.code).toBe(200); - const res2 = await _passwordLoginRequest(request)({ + const res2 = await passwordLoginRequest({ user: 'createTest', password: 'testtest2' }); - expect(res2.code).toBe(200); + expect(res2?.code).toBe(200); }); }); diff --git a/frontend/desktop/src/__tests__/api/e2e/namespace/abdicate.test.ts b/frontend/desktop/src/__tests__/api/e2e/namespace/abdicate.test.ts index 7d082f519f2..a08491b0995 100644 --- a/frontend/desktop/src/__tests__/api/e2e/namespace/abdicate.test.ts +++ b/frontend/desktop/src/__tests__/api/e2e/namespace/abdicate.test.ts @@ -2,92 +2,78 @@ import { _passwordLoginRequest } from '@/api/auth'; import { _abdicateRequest, _createRequest, - _deleteTeamRequest, _inviteMemberRequest, - _modifyRoleRequest, - _nsListRequest, - _reciveMessageRequest, - _removeMemberRequest, - _teamDetailsRequest, _verifyInviteRequest, reciveAction } from '@/api/namespace'; import { UserRole } from '@/types/team'; -import { Session } from 'sealos-desktop-sdk/*'; import * as k8s from '@kubernetes/client-node'; -import { Db, MongoClient } from 'mongodb'; import request from '@/__tests__/api/request'; import { _setAuth, cleanDb, cleanK8s } from '@/__tests__/api/tools'; +import { prisma } from '@/services/backend/db/init'; +import { AccessTokenPayload } from '@/types/token'; +import { jwtDecode } from 'jwt-decode'; const abdicateRequest = _abdicateRequest(request); const createRequest = _createRequest(request); const inviteMemberRequest = _inviteMemberRequest(request); const verifyInviteRequest = _verifyInviteRequest(request); -const passwordLoginRequest = _passwordLoginRequest(request); describe('abdicate team', () => { - let session: Session; - let session2: Session; - let connection: MongoClient; - let db: Db; + let token1: string; + let token2: string; + let payload1: AccessTokenPayload; + let payload2: AccessTokenPayload; const setAuth = _setAuth(request); + const passwordLoginRequest = _passwordLoginRequest(request, setAuth); beforeAll(async () => { //@ts-ignore - const uri = process.env.MONGODB_URI as string; - connection = new MongoClient(uri); - await connection.connect(); - db = connection.db(); const kc = new k8s.KubeConfig(); - await cleanK8s(kc, db); - await cleanDb(db); + await cleanK8s(kc, prisma); + await cleanDb(prisma); + setAuth(); const res = await passwordLoginRequest({ user: 'abdicatetesttest', password: 'testtest' }); // 保证session合理 - expect(res.data?.user).toBeDefined(); - session = res.data as Session; - console.log(session.user); + expect(res!.data).toBeDefined(); + token1 = res!.data!.token; + payload1 = jwtDecode(token1); + console.log('payload1', payload1); + // clear token + setAuth(); const res2 = await passwordLoginRequest({ user: 'abdicatetesttest2', password: 'testtest2' }); // 保证session合理 - expect(res2.data?.user).toBeDefined(); - session2 = res2.data as Session; - setAuth(session); - console.log('abdicate team', session2.user); + expect(res2!.data).toBeDefined(); + token2 = res2!.data!.token; + payload2 = jwtDecode(token2); + console.log('abdicate team', payload2); + setAuth(token1); }, 100000); afterAll(async () => { - await connection.close(); + // await connection.close(); }); describe('owner request', () => { it('null param', async () => { - const res = await abdicateRequest({ ns_uid: '', targetUserId: 'xxx', targetUsername: 'yyy' }); + const res = await abdicateRequest({ ns_uid: '', targetUserCrUid: 'xxx' }); // 没参数 expect(res.code).toBe(400); const res2 = await abdicateRequest({ ns_uid: 'zzz', - targetUserId: '', - targetUsername: 'yyy' + targetUserCrUid: '' }); // 没参数 expect(res2.code).toBe(400); - const res3 = await abdicateRequest({ - ns_uid: 'xxx', - targetUserId: 'xxx', - targetUsername: '' - }); - // 没参数 - expect(res3.code).toBe(400); }); it('abdicate prviate team', async () => { const res = await abdicateRequest({ - ns_uid: session.user.ns_uid, - targetUserId: session2.user.userId, - targetUsername: session2.user.k8s_username + ns_uid: payload1.workspaceUid, + targetUserCrUid: payload2.userCrUid }); expect(res.code).toBe(403); }); - it('abdicate unexist team ', async () => { + it('abdicate invaild team ', async () => { const res = await abdicateRequest({ ns_uid: 'xxx', - targetUserId: session2.user.userId, - targetUsername: session2.user.k8s_username + targetUserCrUid: payload2.userCrUid }); - expect(res.code).toBe(404); + expect(res.code).toBe(400); }); it('abdicate to self', async () => { const nsRes = await createRequest({ teamName: 'team5' }); @@ -96,8 +82,7 @@ describe('abdicate team', () => { const ns_uid = ns.uid; const res = await abdicateRequest({ ns_uid, - targetUserId: session.user.userId, - targetUsername: session.user.k8s_username + targetUserCrUid: payload1.userCrUid }); expect(res.code).toBe(409); }); @@ -110,8 +95,7 @@ describe('abdicate team', () => { // 没被拉入 const res = await abdicateRequest({ ns_uid, - targetUserId: session2.user.userId, - targetUsername: session2.user.k8s_username + targetUserCrUid: payload2.userCrUid }); expect(res.code).toBe(404); }); @@ -126,25 +110,24 @@ describe('abdicate team', () => { const ns = nsRes.data?.namespace!; expect(ns).toBeDefined(); const inviteRes = await inviteMemberRequest({ + targetUserId: payload2.userId, ns_uid: ns.uid, - targetUsername: session2.user.k8s_username, role }); expect(inviteRes.code).toBe(200); // switch people - setAuth(session2); + setAuth(token2); const verifyRes = await verifyInviteRequest({ ns_uid: ns.uid, action: reciveAction.Accepte }); expect(verifyRes.code).toBe(200); // switch people - setAuth(session); + setAuth(token1); // main const res = await abdicateRequest({ ns_uid: ns.uid, - targetUserId: session2.user.userId, - targetUsername: session2.user.k8s_username + targetUserCrUid: payload2.userCrUid }); expect(res.code).toBe(200); }, diff --git a/frontend/desktop/src/__tests__/api/e2e/namespace/create.test.ts b/frontend/desktop/src/__tests__/api/e2e/namespace/create.test.ts index aeaada9c07f..b3f0a6f2796 100644 --- a/frontend/desktop/src/__tests__/api/e2e/namespace/create.test.ts +++ b/frontend/desktop/src/__tests__/api/e2e/namespace/create.test.ts @@ -1,35 +1,38 @@ -import { Session } from 'sealos-desktop-sdk/*'; import * as k8s from '@kubernetes/client-node'; import { _passwordLoginRequest } from '@/api/auth'; import { _setAuth, cleanDb, cleanK8s } from '@/__tests__/api/tools'; import { _createRequest } from '@/api/namespace'; import request from '@/__tests__/api/request'; -import { Db, MongoClient } from 'mongodb'; import { getTeamLimit } from '@/services/enable'; +import { prisma } from '@/services/backend/db/init'; +import { jwtDecode } from 'jwt-decode'; +import { AccessTokenPayload } from '@/types/token'; + describe('Login create', () => { - let session: Session; const createRequest = _createRequest(request); - let db: Db; - let connection: MongoClient; + let token1: string; + let token2: string; + let payload1: AccessTokenPayload; + let payload2: AccessTokenPayload; const setAuth = _setAuth(request); + const passwordLoginRequest = _passwordLoginRequest(request, setAuth); beforeAll(async () => { //@ts-ignore - const uri = process.env.MONGODB_URI as string; // console.log('MONGODB_URI', uri) - connection = new MongoClient(uri); - await connection.connect(); - db = connection.db(); const kc = new k8s.KubeConfig(); - await cleanK8s(kc, db); - await cleanDb(db); - const res = await _passwordLoginRequest(request)({ user: 'createTest', password: 'testtest' }); - expect(res.data?.user).toBeDefined(); - session = res.data!; - setAuth(session); - console.log('create,', session.user); + await cleanK8s(kc, prisma); + await cleanDb(prisma); + setAuth(); + const res = await passwordLoginRequest({ user: 'abdicatetesttest', password: 'testtest' }); + // 保证session合理 + expect(res!.data).toBeDefined(); + token1 = res!.data!.token; + payload1 = jwtDecode(token1); + console.log('payload1', payload1); + setAuth(token1); }, 100000); afterAll(async () => { - await connection.close(); + // await connection.close(); }); it('null team', async () => { const res = await createRequest({ teamName: '' }); @@ -60,13 +63,19 @@ describe('Login create', () => { 'limit 4 team', async (teamName: string, idx: number) => { if (idx === 0) { - const res = await _passwordLoginRequest(request)({ - user: 'createTest5', - password: 'testtest' - }); - expect(res.data?.user).toBeDefined(); - session = res.data!; - setAuth(session); + // const res = await _passwordLoginRequest(request)({ + // user: 'createTest5', + // password: 'testtest' + // }); + // expect(res.data?.user).toBeDefined(); + // session = res.data!; + // setAuth(session); + const res = await passwordLoginRequest({ user: 'abdicatetesttest', password: 'testtest' }); + // 保证session合理 + expect(res!.data).toBeDefined(); + token1 = res!.data!.token; + payload1 = jwtDecode(token1); + setAuth(token1); } const res = await createRequest({ teamName }); console.log('curIdx', idx, 'code', res.code); diff --git a/frontend/desktop/src/__tests__/api/e2e/namespace/delete.test.ts b/frontend/desktop/src/__tests__/api/e2e/namespace/delete.test.ts index ea5277889e6..947bdeae382 100644 --- a/frontend/desktop/src/__tests__/api/e2e/namespace/delete.test.ts +++ b/frontend/desktop/src/__tests__/api/e2e/namespace/delete.test.ts @@ -1,41 +1,35 @@ -import { Session } from 'sealos-desktop-sdk/*'; import * as k8s from '@kubernetes/client-node'; import { _passwordLoginRequest } from '@/api/auth'; import { _setAuth, cleanDb, cleanK8s } from '@/__tests__/api/tools'; -import { _createRequest, _deleteTeamRequest, _swi, _switchRequest } from '@/api/namespace'; +import { _createRequest, _deleteTeamRequest, _switchRequest } from '@/api/namespace'; import request from '@/__tests__/api/request'; -import { Db, MongoClient } from 'mongodb'; +import { AccessTokenPayload } from '@/types/token'; +import { prisma } from '@/services/backend/db/init'; +import { jwtDecode } from 'jwt-decode'; +import { v4 } from 'uuid'; describe('delete team', () => { - let session: Session; - let db: Db; - let connection: MongoClient; - const setAuth = _setAuth(request); const deleteTeamRequest = _deleteTeamRequest(request); const createRequest = _createRequest(request); const switchRequest = _switchRequest(request); + let token1: string; + let payload1: AccessTokenPayload; + const setAuth = _setAuth(request); + const passwordLoginRequest = _passwordLoginRequest(request, setAuth); beforeAll(async () => { //@ts-ignore - const uri = process.env.MONGODB_URI as string; // console.log('MONGODB_URI', uri) - connection = new MongoClient(uri); - await connection.connect(); - db = connection.db(); const kc = new k8s.KubeConfig(); - await cleanK8s(kc, db); - await cleanDb(db); - const res = await _passwordLoginRequest(request)({ - user: 'deleteTesttest', - password: 'testtest' - }); + await cleanK8s(kc, prisma); + await cleanDb(prisma); + setAuth(); + const res = await passwordLoginRequest({ user: 'abdicatetesttest', password: 'testtest' }); // 保证session合理 - expect(res.data?.user).toBeDefined(); - session = res.data!; - setAuth(session); - console.log('delete', session.user); - }, 10000); - afterAll(async () => { - await connection.close(); - }); + expect(res!.data).toBeDefined(); + token1 = res!.data!.token; + payload1 = jwtDecode(token1); + console.log('payload1', payload1); + setAuth(token1); + }, 100000); it('null team', async () => { const res = await deleteTeamRequest({ ns_uid: '' }); // 没参数 @@ -48,13 +42,17 @@ describe('delete team', () => { const res3 = await deleteTeamRequest({ ns_uid: null }); // 没参数 expect(res3.code).toBe(400); + // 参数有问题 + const res4 = await deleteTeamRequest({ ns_uid: 'asdfsadf' }); + // 没参数 + expect(res4.code).toBe(400); }); it('delete prviate team vaild', async () => { - const res = await deleteTeamRequest({ ns_uid: session.user.ns_uid }); + const res = await deleteTeamRequest({ ns_uid: payload1.workspaceUid }); expect(res.code).toBe(403); }); it('delete unkown team', async () => { - const res = await deleteTeamRequest({ ns_uid: 'xasz' }); + const res = await deleteTeamRequest({ ns_uid: v4() }); expect(res.code).toBe(404); }); it('already exist team', async () => { @@ -64,14 +62,14 @@ describe('delete team', () => { expect(res.code).toBe(200); }, 10000); it('delete current team', async () => { - setAuth(session); + setAuth(token1); const ns = await createRequest({ teamName: 'testssTeam' }); expect(ns.data).toBeDefined(); const ns_uid = ns.data!.namespace.uid; const res = await switchRequest(ns_uid); expect(res.code).toBe(200); expect(res.data).toBeDefined(); - setAuth(res.data!); + setAuth(res.data!.token); const res2 = await deleteTeamRequest({ ns_uid }); expect(res2.code).toBe(403); }); diff --git a/frontend/desktop/src/__tests__/api/e2e/namespace/invite.test.ts b/frontend/desktop/src/__tests__/api/e2e/namespace/invite.test.ts index f4be7193465..7042d0706de 100644 --- a/frontend/desktop/src/__tests__/api/e2e/namespace/invite.test.ts +++ b/frontend/desktop/src/__tests__/api/e2e/namespace/invite.test.ts @@ -4,76 +4,77 @@ import { _createRequest, _deleteTeamRequest, _inviteMemberRequest, - _modifyRoleRequest, _nsListRequest, - _reciveMessageRequest, - _removeMemberRequest, - _teamDetailsRequest, _verifyInviteRequest, reciveAction } from '@/api/namespace'; import { NamespaceDto, UserRole } from '@/types/team'; import { Session } from 'sealos-desktop-sdk/*'; import * as k8s from '@kubernetes/client-node'; -import { Db, MongoClient } from 'mongodb'; import request from '@/__tests__/api/request'; import { _setAuth, cleanDb, cleanK8s } from '@/__tests__/api/tools'; import { INVITE_LIMIT } from '@/types'; +import { AccessTokenPayload } from '@/types/token'; +import { prisma } from '@/services/backend/db/init'; +import { jwtDecode } from 'jwt-decode'; const createRequest = _createRequest(request); const inviteMemberRequest = _inviteMemberRequest(request); const verifyInviteRequest = _verifyInviteRequest(request); -const passwordLoginRequest = _passwordLoginRequest(request); +const listNamespaceRequest = _nsListRequest(request); describe('invite member', () => { - let session: Session; - let session2: Session; - let connection: MongoClient; - let db: Db; - let ns: NamespaceDto; + let token1: string; + let payload1: AccessTokenPayload; + let token2: string; + let payload2: AccessTokenPayload; const setAuth = _setAuth(request); + let ns: NamespaceDto; + const passwordLoginRequest = _passwordLoginRequest(request, setAuth); beforeAll(async () => { //@ts-ignore - const uri = process.env.MONGODB_URI as string; - connection = new MongoClient(uri); - await connection.connect(); - db = connection.db(); + // console.log('MONGODB_URI', uri) const kc = new k8s.KubeConfig(); - await cleanK8s(kc, db); - await cleanDb(db); - const res = await passwordLoginRequest({ user: 'inviteTesttest', password: 'testtest' }); + await cleanK8s(kc, prisma); + await cleanDb(prisma); + setAuth(); + const res = await passwordLoginRequest({ user: 'abdicatetesttest', password: 'testtest' }); // 保证session合理 - expect(res.data?.user).toBeDefined(); - session = res.data as Session; + expect(res!.data).toBeDefined(); + token1 = res!.data!.token; + payload1 = jwtDecode(token1); + console.log('payload1', payload1); + setAuth(); const res2 = await passwordLoginRequest({ user: 'inviteTesttest2', password: 'testtest2' }); // 保证session合理 - expect(res2.data?.user).toBeDefined(); - session2 = res2.data as Session; - setAuth(session); + expect(res2!.data).toBeDefined(); + token2 = res2!.data!.token; + payload2 = jwtDecode(token2); + setAuth(token1); const nsRes = await createRequest({ teamName: 'teamZero' }); expect(nsRes.data?.namespace).toBeDefined(); ns = nsRes.data?.namespace!; }, 100000); - afterAll(async () => { - await connection.close(); - }); + // afterAll(async () => { + // await connection.close(); + // }); describe('invite Owner', () => { it('invite Owner', async () => { const res = await inviteMemberRequest({ ns_uid: ns.uid, - targetUsername: session2.user.k8s_username, + targetUserId: payload2.userCrName, role: UserRole.Owner }); expect(res.code).toBe(403); }); }); - describe.each([[UserRole.Developer]])('owner request', (role) => { + describe.each([[UserRole.Developer], [UserRole.Manager]])('owner request', (role) => { it('null param', async () => { - setAuth(session); - const res = await inviteMemberRequest({ ns_uid: '', targetUsername: 'xxx', role }); + setAuth(token1); + const res = await inviteMemberRequest({ ns_uid: '', targetUserId: 'xxx', role }); // 没参数 expect(res.code).toBe(400); const res2 = await inviteMemberRequest({ ns_uid: 'xxx', - targetUsername: '', + targetUserId: '', role }); @@ -81,74 +82,76 @@ describe('invite member', () => { expect(res2.code).toBe(400); const res3 = await inviteMemberRequest({ ns_uid: 'xxx', - targetUsername: 'yyy', + targetUserId: 'yyy', role: '' as any }); // 没参数 expect(res3.code).toBe(400); }); it('invite prviate team', async () => { - setAuth(session); + setAuth(token1); const res = await inviteMemberRequest({ - ns_uid: session.user.ns_uid, - targetUsername: session.user.k8s_username, + ns_uid: payload1.workspaceUid, + targetUserId: payload2.userId, role: role }); + // bug expect(res.code).toBe(403); }); it('invite unkown member', async () => { - setAuth(session); + setAuth(token1); const res = await inviteMemberRequest({ ns_uid: ns.uid, - targetUsername: 'yyy', + targetUserId: 'yyy', role }); expect(res.code).toBe(404); }); it('invite self', async () => { - setAuth(session); + setAuth(token2); const ns_uid = ns.uid; const res = await inviteMemberRequest({ ns_uid, - targetUsername: session.user.k8s_username, + targetUserId: payload1.userId, role }); expect(res.code).toBe(403); }); it('invite exist member', async () => { - setAuth(session); - const nsRes = await createRequest({ teamName: 'teamLimit2' }); + setAuth(token1); + const nsRes = await createRequest({ teamName: 'teamLimit2' + role }); expect(nsRes.data?.namespace).toBeDefined(); const ns = nsRes.data?.namespace!; const ns_uid = ns.uid; const res = await inviteMemberRequest({ ns_uid, - targetUsername: session2.user.k8s_username, + targetUserId: payload2.userId, role }); + console.log(res, ns, payload1); expect(res.code).toBe(200); - setAuth(session2); + setAuth(token2); const res2 = await verifyInviteRequest({ action: reciveAction.Accepte, ns_uid }); expect(res2.code).toBe(200); - setAuth(session); + setAuth(token1); const res3 = await inviteMemberRequest({ ns_uid, - targetUsername: session2.user.k8s_username, + targetUserId: payload2.userId, role }); expect(res3.code).toBe(403); - }); - describe('invite limit', () => { - let limitSession: Session; - const otherSessions: Session[] = []; + }, 10000); + describe.skip('invite limit', () => { + let limitToken: string; + const otherSessions: string[] = []; let ns: NamespaceDto; beforeAll(async () => { const res = await passwordLoginRequest({ user: `invitelimitTest`, password: 'testtest2' }); // 保证session合理 - expect(res.data?.user).toBeDefined(); - limitSession = res.data as Session; - setAuth(limitSession); + expect(res!.data).toBeDefined(); + limitToken = res!.data!.token; + setAuth(limitToken); const nsRes = await createRequest({ teamName: 'teamLimitl' }); expect(nsRes.data?.namespace).toBeDefined(); ns = nsRes.data?.namespace!; @@ -160,13 +163,13 @@ describe('invite member', () => { password: 'testtest2' }); // 保证session合理 - expect(res.data?.user).toBeDefined(); - const otherSession = res.data as Session; + expect(res?.data?.token).toBeDefined(); + const otherSession = res!.data!.token; otherSessions.push(otherSession); - setAuth(limitSession); + setAuth(limitToken); const inviteRes = await inviteMemberRequest({ ns_uid: ns.uid, - targetUsername: otherSession.user.k8s_username, + targetUserId: jwtDecode(otherSession).userCrName, role }); console.log(i); diff --git a/frontend/desktop/src/__tests__/api/e2e/namespace/modify.test.ts b/frontend/desktop/src/__tests__/api/e2e/namespace/modify.test.ts index 4e2ebdfb1aa..b0677f9e066 100644 --- a/frontend/desktop/src/__tests__/api/e2e/namespace/modify.test.ts +++ b/frontend/desktop/src/__tests__/api/e2e/namespace/modify.test.ts @@ -1,74 +1,65 @@ import { _passwordLoginRequest } from '@/api/auth'; import { - _abdicateRequest, _createRequest, - _deleteTeamRequest, _inviteMemberRequest, _modifyRoleRequest, - _nsListRequest, - _reciveMessageRequest, - _removeMemberRequest, - _teamDetailsRequest, _verifyInviteRequest, reciveAction } from '@/api/namespace'; import { NamespaceDto, UserRole } from '@/types/team'; -import { Session } from 'sealos-desktop-sdk/*'; import * as k8s from '@kubernetes/client-node'; -import { Db, MongoClient } from 'mongodb'; import request from '@/__tests__/api/request'; import { _setAuth, cleanDb, cleanK8s } from '@/__tests__/api/tools'; +import { AccessTokenPayload } from '@/types/token'; +import { prisma } from '@/services/backend/db/init'; +import { jwtDecode } from 'jwt-decode'; +import { v4 } from 'uuid'; + const createRequest = _createRequest(request); const inviteMemberRequest = _inviteMemberRequest(request); const verifyInviteRequest = _verifyInviteRequest(request); -const passwordLoginRequest = _passwordLoginRequest(request); const modifyRoleRequest = _modifyRoleRequest(request); describe('modify role', () => { - let session: Session; - let session2: Session; - let connection: MongoClient; - let db: Db; + let token1: string; + let token2: string; + let payload1: AccessTokenPayload; + let payload2: AccessTokenPayload; let ns: NamespaceDto; const setAuth = _setAuth(request); + const passwordLoginRequest = _passwordLoginRequest(request, setAuth); beforeAll(async () => { - //@ts-ignore - const uri = process.env.MONGODB_URI as string; - connection = new MongoClient(uri); - await connection.connect(); - db = connection.db(); const kc = new k8s.KubeConfig(); - await cleanK8s(kc, db); - await cleanDb(db); - const res = await passwordLoginRequest({ user: 'modifytesttest', password: 'testtest' }); + await cleanK8s(kc, prisma); + await cleanDb(prisma); + setAuth(); + const res = await passwordLoginRequest({ user: 'removetesttest', password: 'testtest' }); // 保证session合理 - expect(res.data?.user).toBeDefined(); - session = res.data as Session; - const res2 = await passwordLoginRequest({ user: 'modifytesttest2', password: 'testtest2' }); + token1 = res!.data!.token; + expect(token1).toBeDefined(); + payload1 = jwtDecode(token1); + setAuth(); + const res2 = await passwordLoginRequest({ user: 'removetesttest2', password: 'testtest2' }); // 保证session合理 - expect(res2.data?.user).toBeDefined(); - session2 = res2.data as Session; - setAuth(session); + token2 = res2!.data!.token; + expect(token2).toBeDefined(); + setAuth(token1); + payload2 = jwtDecode(token2); const nsRes = await createRequest({ teamName: 'teamZero' }); expect(nsRes.data?.namespace).toBeDefined(); ns = nsRes.data?.namespace!; }, 100000); - afterAll(async () => { - await connection.close(); - }); describe('owner request', () => { it('null param', async () => { const res = await modifyRoleRequest({ ns_uid: '', - tUserId: 'xxx', - tK8s_username: 'yyy', + targetUserCrUid: 'xxx', tRole: UserRole.Developer }); // 没参数 expect(res.code).toBe(400); const res2 = await modifyRoleRequest({ - ns_uid: 'xxx', - tUserId: '', - tK8s_username: 'yyy', + ns_uid: v4(), + targetUserCrUid: '', tRole: UserRole.Developer }); @@ -76,16 +67,20 @@ describe('modify role', () => { expect(res2.code).toBe(400); const res3 = await modifyRoleRequest({ ns_uid: 'xxx', - tUserId: 'xxx', - tK8s_username: '', + targetUserCrUid: v4(), + tRole: UserRole.Developer + }); + expect(res2.code).toBe(400); + const res5 = await modifyRoleRequest({ + ns_uid: v4(), + targetUserCrUid: 'xxx', tRole: UserRole.Developer }); // 没参数 - expect(res3.code).toBe(400); + expect(res5.code).toBe(400); const res4 = await modifyRoleRequest({ - ns_uid: 'xxx', - tUserId: 'xxx', - tK8s_username: 'yyy', + ns_uid: v4(), + targetUserCrUid: v4(), tRole: '' as any }); // 没参数 @@ -95,9 +90,8 @@ describe('modify role', () => { 'modify prviate team', async (role) => { const res = await modifyRoleRequest({ - ns_uid: session.user.ns_uid, - tUserId: session.user.userId, - tK8s_username: session.user.k8s_username, + ns_uid: payload1.workspaceUid, + targetUserCrUid: payload1.userCrUid, tRole: role }); expect(res.code).toBe(403); @@ -108,23 +102,23 @@ describe('modify role', () => { async (role) => { const res = await modifyRoleRequest({ ns_uid: ns.uid, - tUserId: 'xxx', - tK8s_username: 'yyy', + targetUserCrUid: payload2.userCrUid, tRole: role }); - expect(res.code).toBe(403); + expect(res.code).toBe(404); } ); it.each([[UserRole.Developer], [UserRole.Manager], [UserRole.Owner]])( 'modify to self', async (role) => { + setAuth(token1); const ns_uid = ns.uid; const res = await modifyRoleRequest({ ns_uid, - tUserId: session.user.userId, - tK8s_username: session.user.k8s_username, + targetUserCrUid: payload1.userCrUid, tRole: role }); + console.log(res); expect(res.code).toBe(403); } ); @@ -135,19 +129,19 @@ describe('modify role', () => { ])( 'modify member', async (role, teamName) => { - setAuth(session); + setAuth(token1); // setup const nsRes = await createRequest({ teamName }); const ns = nsRes.data?.namespace!; expect(ns).toBeDefined(); const inviteRes = await inviteMemberRequest({ ns_uid: ns.uid, - targetUsername: session2.user.k8s_username, + targetUserId: payload2.userId, role }); expect(inviteRes.code).toBe(200); // switch people - setAuth(session2); + setAuth(token2); const verifyRes = await verifyInviteRequest({ ns_uid: ns.uid, action: reciveAction.Accepte @@ -155,13 +149,11 @@ describe('modify role', () => { expect(verifyRes.code).toBe(200); // switch people - setAuth(session); - console.log('main', session); + setAuth(token1); // main const res = await modifyRoleRequest({ ns_uid: ns.uid, - tUserId: session2.user.userId, - tK8s_username: session2.user.k8s_username, + targetUserCrUid: payload2.userCrUid, tRole: role }); console.log(res); diff --git a/frontend/desktop/src/__tests__/api/e2e/namespace/recive.test.ts b/frontend/desktop/src/__tests__/api/e2e/namespace/recive.test.ts index 60d2005b9dd..6b1a626d16d 100644 --- a/frontend/desktop/src/__tests__/api/e2e/namespace/recive.test.ts +++ b/frontend/desktop/src/__tests__/api/e2e/namespace/recive.test.ts @@ -1,62 +1,52 @@ import { _passwordLoginRequest } from '@/api/auth'; import { - _abdicateRequest, _createRequest, - _deleteTeamRequest, _inviteMemberRequest, - _modifyRoleRequest, - _nsListRequest, _reciveMessageRequest, - _removeMemberRequest, - _teamDetailsRequest, _verifyInviteRequest, reciveAction } from '@/api/namespace'; import { NamespaceDto, UserRole } from '@/types/team'; -import { Session } from 'sealos-desktop-sdk/*'; import * as k8s from '@kubernetes/client-node'; -import { Db, MongoClient } from 'mongodb'; import request from '@/__tests__/api/request'; import { _setAuth, cleanDb, cleanK8s } from '@/__tests__/api/tools'; +import { AccessTokenPayload } from '@/types/token'; +import { prisma } from '@/services/backend/db/init'; +import { jwtDecode } from 'jwt-decode'; + const createRequest = _createRequest(request); const inviteMemberRequest = _inviteMemberRequest(request); const verifyInviteRequest = _verifyInviteRequest(request); -const passwordLoginRequest = _passwordLoginRequest(request); -const modifyRoleRequest = _modifyRoleRequest(request); const reciveMessageRequest = _reciveMessageRequest(request); -const abdicateRequest = _abdicateRequest(request); describe('recive message', () => { - let session: Session; - let session2: Session; - let connection: MongoClient; - let db: Db; + let token1: string; + let token2: string; + let payload1: AccessTokenPayload; + let payload2: AccessTokenPayload; let ns: NamespaceDto; const setAuth = _setAuth(request); + const passwordLoginRequest = _passwordLoginRequest(request, setAuth); beforeAll(async () => { - //@ts-ignore - const uri = process.env.MONGODB_URI as string; - connection = new MongoClient(uri); - await connection.connect(); - db = connection.db(); const kc = new k8s.KubeConfig(); - await cleanK8s(kc, db); - await cleanDb(db); - const res = await passwordLoginRequest({ user: 'recivetesttest', password: 'testtest' }); + await cleanK8s(kc, prisma); + await cleanDb(prisma); + setAuth(); + const res = await passwordLoginRequest({ user: 'removetesttest', password: 'testtest' }); // 保证session合理 - expect(res.data?.user).toBeDefined(); - session = res.data as Session; - const res2 = await passwordLoginRequest({ user: 'recivetesttest2', password: 'testtest2' }); + token1 = res!.data!.token; + expect(token1).toBeDefined(); + payload1 = jwtDecode(token1); + setAuth(); + const res2 = await passwordLoginRequest({ user: 'removetesttest2', password: 'testtest2' }); // 保证session合理 - expect(res2.data?.user).toBeDefined(); - session2 = res2.data as Session; - setAuth(session); + token2 = res2!.data!.token; + expect(token2).toBeDefined(); + setAuth(token1); + payload2 = jwtDecode(token2); const nsRes = await createRequest({ teamName: 'teamZero' }); expect(nsRes.data?.namespace).toBeDefined(); ns = nsRes.data?.namespace!; }, 100000); - afterAll(async () => { - await connection.close(); - }); it('null message', async () => { const res = await reciveMessageRequest(); expect(res.code).toBe(200); @@ -66,10 +56,10 @@ describe('recive message', () => { const res1 = await inviteMemberRequest({ ns_uid: ns.uid, role: UserRole.Developer, - targetUsername: session2.user.k8s_username + targetUserId: payload2.userId }); expect(res1.code).toBe(200); - setAuth(session2); + setAuth(token2); const res2 = await reciveMessageRequest(); expect(res2.code).toBe(200); expect(res2.data?.messages.length).toBe(1); diff --git a/frontend/desktop/src/__tests__/api/e2e/namespace/remove.test.ts b/frontend/desktop/src/__tests__/api/e2e/namespace/remove.test.ts index d9729a8e438..acda2a55dce 100644 --- a/frontend/desktop/src/__tests__/api/e2e/namespace/remove.test.ts +++ b/frontend/desktop/src/__tests__/api/e2e/namespace/remove.test.ts @@ -2,86 +2,80 @@ import { _passwordLoginRequest } from '@/api/auth'; import { _abdicateRequest, _createRequest, - _deleteTeamRequest, _inviteMemberRequest, - _modifyRoleRequest, - _nsListRequest, - _reciveMessageRequest, _removeMemberRequest, - _teamDetailsRequest, _verifyInviteRequest, reciveAction } from '@/api/namespace'; import { NamespaceDto, UserRole } from '@/types/team'; -import { Session } from 'sealos-desktop-sdk/*'; import * as k8s from '@kubernetes/client-node'; -import { Db, MongoClient } from 'mongodb'; import request from '@/__tests__/api/request'; import { _setAuth, cleanDb, cleanK8s } from '@/__tests__/api/tools'; +import { AccessTokenPayload } from '@/types/token'; +import { prisma } from '@/services/backend/db/init'; +import { jwtDecode } from 'jwt-decode'; +import { v4 } from 'uuid'; const abdicateRequest = _abdicateRequest(request); const createRequest = _createRequest(request); const inviteMemberRequest = _inviteMemberRequest(request); const verifyInviteRequest = _verifyInviteRequest(request); -const passwordLoginRequest = _passwordLoginRequest(request); const removeMember = _removeMemberRequest(request); describe('remove member', () => { - let session: Session; - let session2: Session; - let connection: MongoClient; - let db: Db; + let token1: string; + let token2: string; + let payload1: AccessTokenPayload; + let payload2: AccessTokenPayload; let ns: NamespaceDto; const setAuth = _setAuth(request); + const passwordLoginRequest = _passwordLoginRequest(request, setAuth); beforeAll(async () => { - //@ts-ignore - const uri = process.env.MONGODB_URI as string; - connection = new MongoClient(uri); - await connection.connect(); - db = connection.db(); const kc = new k8s.KubeConfig(); - await cleanK8s(kc, db); - await cleanDb(db); + await cleanK8s(kc, prisma); + await cleanDb(prisma); + setAuth(); const res = await passwordLoginRequest({ user: 'removetesttest', password: 'testtest' }); // 保证session合理 - expect(res.data?.user).toBeDefined(); - session = res.data as Session; + token1 = res!.data!.token; + expect(token1).toBeDefined(); + payload1 = jwtDecode(token1); + setAuth(); const res2 = await passwordLoginRequest({ user: 'removetesttest2', password: 'testtest2' }); // 保证session合理 - expect(res2.data?.user).toBeDefined(); - session2 = res2.data as Session; - setAuth(session); + token2 = res2!.data!.token; + expect(token2).toBeDefined(); + setAuth(token1); + payload2 = jwtDecode(token2); const nsRes = await createRequest({ teamName: 'teamZero' }); expect(nsRes.data?.namespace).toBeDefined(); ns = nsRes.data?.namespace!; }, 100000); - afterAll(async () => { - await connection.close(); - }); it('null param', async () => { - const res = await removeMember({ ns_uid: '', tUserId: 'xxx', tK8s_username: 'yyy' }); + const res = await removeMember({ ns_uid: '', targetUserCrUid: v4() }); // 没参数 expect(res.code).toBe(400); - const res2 = await removeMember({ ns_uid: 'xxx', tUserId: '', tK8s_username: 'yyy' }); - // 没参数 + const res2 = await removeMember({ ns_uid: v4(), targetUserCrUid: '' }); + // 非uuid expect(res2.code).toBe(400); - const res3 = await removeMember({ ns_uid: 'xxx', tUserId: 'xxx', tK8s_username: '' }); + const res3 = await removeMember({ ns_uid: v4(), targetUserCrUid: 'xxx' }); // 没参数 expect(res3.code).toBe(400); - }); - it('remove ', async () => { - const res = await abdicateRequest({ - ns_uid: session.user.ns_uid, - targetUserId: session2.user.userId, - targetUsername: session2.user.k8s_username - }); - expect(res.code).toBe(403); + const res4 = await removeMember({ ns_uid: 'xxx', targetUserCrUid: v4() }); + // 非uuid + expect(res4.code).toBe(400); }); it('remove unexist user', async () => { + setAuth(token1); const res = await removeMember({ - ns_uid: 'xxx', - tUserId: session2.user.userId, - tK8s_username: session2.user.k8s_username + ns_uid: v4(), + targetUserCrUid: payload2.userCrUid + }); + expect(res.code).toBe(403); + setAuth(token1); + const res2 = await removeMember({ + ns_uid: ns.uid, + targetUserCrUid: payload2.userCrUid }); - expect(res.code).toBe(404); + expect(res2.code).toBe(404); }); it('remove self', async () => { const nsRes = await createRequest({ teamName: 'team5' }); @@ -90,30 +84,28 @@ describe('remove member', () => { const ns_uid = ns.uid; const res = await removeMember({ ns_uid, - tUserId: session.user.userId, - tK8s_username: session.user.k8s_username + targetUserCrUid: payload1.userCrUid }); expect(res.code).toBe(403); }); it('remove others', async () => { - setAuth(session); + setAuth(token1); const res = await inviteMemberRequest({ ns_uid: ns.uid, - targetUsername: session2.user.k8s_username, + targetUserId: payload2.userId, role: UserRole.Developer }); expect(res.code).toBe(200); - setAuth(session2); + setAuth(token2); const res2 = await verifyInviteRequest({ ns_uid: ns.uid, action: reciveAction.Accepte }); expect(res2.code).toBe(200); - setAuth(session); + setAuth(token1); const res3 = await removeMember({ ns_uid: ns.uid, - tUserId: session2.user.userId, - tK8s_username: session2.user.k8s_username + targetUserCrUid: payload2.userCrUid }); expect(res3.code).toBe(200); }); diff --git a/frontend/desktop/src/__tests__/api/e2e/namespace/switch.test.ts b/frontend/desktop/src/__tests__/api/e2e/namespace/switch.test.ts index e0448e56e55..99f24315430 100644 --- a/frontend/desktop/src/__tests__/api/e2e/namespace/switch.test.ts +++ b/frontend/desktop/src/__tests__/api/e2e/namespace/switch.test.ts @@ -1,68 +1,60 @@ import { _passwordLoginRequest } from '@/api/auth'; -import { - _abdicateRequest, - _createRequest, - _deleteTeamRequest, - _inviteMemberRequest, - _modifyRoleRequest, - _nsListRequest, - _reciveMessageRequest, - _removeMemberRequest, - _switchRequest, - _teamDetailsRequest, - _verifyInviteRequest, - reciveAction -} from '@/api/namespace'; -import { NamespaceDto, UserRole } from '@/types/team'; -import { Session } from 'sealos-desktop-sdk/*'; +import { _createRequest, _switchRequest } from '@/api/namespace'; +import { NamespaceDto } from '@/types/team'; import * as k8s from '@kubernetes/client-node'; -import { Db, MongoClient } from 'mongodb'; import request from '@/__tests__/api/request'; import { _setAuth, cleanDb, cleanK8s } from '@/__tests__/api/tools'; +import { AccessTokenPayload } from '@/types/token'; +import { prisma } from '@/services/backend/db/init'; +import { jwtDecode } from 'jwt-decode'; + const createRequest = _createRequest(request); -const inviteMemberRequest = _inviteMemberRequest(request); -const verifyInviteRequest = _verifyInviteRequest(request); -const passwordLoginRequest = _passwordLoginRequest(request); -const modifyRoleRequest = _modifyRoleRequest(request); const switchRequest = _switchRequest(request); -describe('modify role', () => { - let session: Session; - let session2: Session; - let connection: MongoClient; - let db: Db; +describe('switch ns', () => { + let token1: string; let ns: NamespaceDto; + let payload1: AccessTokenPayload; const setAuth = _setAuth(request); + const passwordLoginRequest = _passwordLoginRequest(request, setAuth); beforeAll(async () => { //@ts-ignore - const uri = process.env.MONGODB_URI as string; - connection = new MongoClient(uri); - await connection.connect(); - db = connection.db(); + // console.log('MONGODB_URI', uri) const kc = new k8s.KubeConfig(); - await cleanK8s(kc, db); - await cleanDb(db); - const res = await passwordLoginRequest({ user: 'modifytesttest', password: 'testtest' }); + await cleanK8s(kc, prisma); + await cleanDb(prisma); + setAuth(); + const res = await passwordLoginRequest({ user: 'abdicatetesttest', password: 'testtest' }); // 保证session合理 - expect(res.data?.user).toBeDefined(); - session = res.data as Session; - setAuth(session); + expect(res!.data).toBeDefined(); + token1 = res!.data!.token; + payload1 = jwtDecode(token1); + setAuth(token1); const nsRes = await createRequest({ teamName: 'teamZero' }); - expect(nsRes.data?.namespace).toBeDefined(); - ns = nsRes.data?.namespace!; + expect(nsRes.data).toBeDefined(); + ns = nsRes.data!.namespace; }, 100000); afterAll(async () => { - await connection.close(); + // await connection.close(); }); it('easy switch', async () => { const res1 = await switchRequest(ns.uid); expect(res1.code).toBe(200); - expect(res1.data).toMatchObject({ - user: { - k8s_username: session.user.k8s_username, - userId: session.user.userId, - ns_uid: ns.uid, - nsid: ns.id - } + const { workspaceUid, workspaceId, userId, userCrUid, userCrName, userUid } = + jwtDecode(res1.data!.token); + expect>({ + workspaceUid, + workspaceId, + userCrName, + userCrUid, + userUid, + userId + }).toMatchObject({ + workspaceUid: ns.uid, + workspaceId: ns.id, + userCrName: payload1.userCrName, + userCrUid: payload1.userCrUid, + userUid: payload1.userUid, + userId: payload1.userId }); }); }); diff --git a/frontend/desktop/src/__tests__/api/e2e/namespace/withoutSession.test.ts b/frontend/desktop/src/__tests__/api/e2e/namespace/withoutSession.test.ts index ad90b6ab1ad..0e85339b3c9 100644 --- a/frontend/desktop/src/__tests__/api/e2e/namespace/withoutSession.test.ts +++ b/frontend/desktop/src/__tests__/api/e2e/namespace/withoutSession.test.ts @@ -1,5 +1,4 @@ import request from '@/__tests__/api/request'; -import { _passwordLoginRequest } from '@/api/auth'; import { _abdicateRequest, _createRequest, @@ -36,8 +35,7 @@ describe('notLogin', () => { it('abdicate team', async () => { const res = await abdicateRequest({ ns_uid: 'xxx', - targetUserId: 'xxx', - targetUsername: 'XXX' + targetUserCrUid: 'xxx' }); expect(res.code).toBe(401); }); @@ -45,15 +43,14 @@ describe('notLogin', () => { const res = await inviteMemberRequest({ ns_uid: 'xxx', role: UserRole.Developer, - targetUsername: 'XXX' + targetUserId: 'XXX' }); expect(res.code).toBe(401); }); it('modify role', async () => { const res = await modifyRoleRequest({ ns_uid: 'xxx', - tUserId: 'xxx', - tK8s_username: 'XXX', + targetUserCrUid: 'xxx', tRole: UserRole.Manager }); expect(res.code).toBe(401); @@ -67,7 +64,7 @@ describe('notLogin', () => { expect(res.code).toBe(401); }); it('remove member', async () => { - const res = await removeMemberRequest({ ns_uid: 'xxx', tUserId: 'xxx', tK8s_username: 'XXX' }); + const res = await removeMemberRequest({ ns_uid: 'xxx', targetUserCrUid: 'xxx' }); expect(res.code).toBe(401); }); it('team details', async () => { diff --git a/frontend/desktop/src/__tests__/api/e2e/v1alpha/signup.test.ts b/frontend/desktop/src/__tests__/api/e2e/v1alpha/signup.test.ts index 78159300bb9..c9b3cf158cc 100644 --- a/frontend/desktop/src/__tests__/api/e2e/v1alpha/signup.test.ts +++ b/frontend/desktop/src/__tests__/api/e2e/v1alpha/signup.test.ts @@ -1,12 +1,10 @@ import { Session } from 'sealos-desktop-sdk/*'; import * as k8s from '@kubernetes/client-node'; -import { _passwordLoginRequest, _passwordModifyRequest } from '@/api/auth'; import { _setAuth, cleanDb, cleanK8s } from '@/__tests__/api/tools'; -import { _createRequest } from '@/api/namespace'; import request from '@/__tests__/api/request'; -import { Db, MongoClient } from 'mongodb'; import { ApiResp } from '@/types'; import RandExp from 'randexp'; +import { prisma } from '@/services/backend/db/init'; export const getUserAndPassword = () => { const userFactory = new RandExp('[a-zA-Z0-9_-]{10,16}'); @@ -24,22 +22,12 @@ describe('v1alpha/signup', () => { '/api/v1alpha/password/signin', data ); - let db: Db; - let connection: MongoClient; const setAuth = _setAuth(request); beforeAll(async () => { - //@ts-ignore - const uri = process.env.MONGODB_URI as string; - connection = new MongoClient(uri); - await connection.connect(); - db = connection.db(); const kc = new k8s.KubeConfig(); - await cleanK8s(kc, db); - await cleanDb(db); + await cleanK8s(kc, prisma); + await cleanDb(prisma); }, 100000); - afterAll(async () => { - await connection.close(); - }); it.each([[1], [2], [3], [4], [5]])( 'sign up test', async (time) => { @@ -57,7 +45,7 @@ describe('v1alpha/signup', () => { const res = await signinRequest(user); expect(res.code).toBe(403); } - setAuth({}); + setAuth(); }, 10000 ); diff --git a/frontend/desktop/src/__tests__/api/tools.ts b/frontend/desktop/src/__tests__/api/tools.ts index 87317977903..3056dffdd6d 100644 --- a/frontend/desktop/src/__tests__/api/tools.ts +++ b/frontend/desktop/src/__tests__/api/tools.ts @@ -1,39 +1,23 @@ -import { _passwordLoginRequest } from '@/api/auth'; -import { - _abdicateRequest, - _createRequest, - _deleteTeamRequest, - _inviteMemberRequest, - _modifyRoleRequest, - _nsListRequest, - _reciveMessageRequest, - _removeMemberRequest, - _teamDetailsRequest, - _verifyInviteRequest -} from '@/api/namespace'; -import { Namespace } from '@/types/team'; -import { Session } from 'sealos-desktop-sdk/*'; import * as k8s from '@kubernetes/client-node'; -import { Db } from 'mongodb'; -import { User } from '@/types/user'; import { AxiosInstance } from 'axios'; +import { PrismaClient } from 'prisma/region/generated/client'; +import { PrismaClient as GlobalPrismaClient } from 'prisma/global/generated/client'; -export const cleanK8s = async (kc: k8s.KubeConfig, db: Db) => { +export const cleanK8s = async (kc: k8s.KubeConfig, prisma: PrismaClient) => { kc.loadFromDefault(); - const userCollection = db.collection('user'); - const users = await userCollection.find({}).toArray(); - Promise.all( - users.map(async (user) => { + console.log('cleanK8s'); + const userCrs = await prisma.userCr.findMany(); + await Promise.all( + userCrs.map(async (user) => { try { await kc .makeApiClient(k8s.CustomObjectsApi) - .deleteClusterCustomObject('user.sealos.io', 'v1', 'users', user.k8s_users![0].name); + .deleteClusterCustomObject('user.sealos.io', 'v1', 'users', user.crName); } catch {} }) ); - const nsCollection = db.collection('namespace'); - const namespaces = await nsCollection.find({}).toArray(); - Promise.all( + const namespaces = await prisma.workspace.findMany(); + await Promise.all( namespaces.map(async (ns) => { try { await kc.makeApiClient(k8s.CoreV1Api).deleteNamespace(ns.id); @@ -41,20 +25,23 @@ export const cleanK8s = async (kc: k8s.KubeConfig, db: Db) => { }) ); }; -export const cleanDb = async (db: Db) => { - const collections = await db.collections(); - Promise.all( - collections.map(async (collection) => { - await collection.deleteMany({}); - }) - ); +export const cleanDb = async (prisma: PrismaClient) => { + console.log('cleanDb'); + await prisma.userWorkspace.deleteMany(); + await prisma.workspace.deleteMany(); + await prisma.userCr.deleteMany(); +}; +export const cleanGlobalDb = async (prisma: GlobalPrismaClient) => { + console.log('cleanDb'); + await prisma.oauthProvider.deleteMany(); + await prisma.user.deleteMany(); }; -export const _setAuth = (request: AxiosInstance) => (session: Partial) => { +export const _setAuth = (request: AxiosInstance) => (token?: string) => { request.interceptors.request.clear(); - session?.token && + token && request.interceptors.request.use((config) => { - config.headers.Authorization = session.token; + config.headers.Authorization = token; return config; }); }; diff --git a/frontend/desktop/src/__tests__/unit/backend/persistImage.test.ts b/frontend/desktop/src/__tests__/unit/backend/persistImage.test.ts new file mode 100644 index 00000000000..b3a1d0e286f --- /dev/null +++ b/frontend/desktop/src/__tests__/unit/backend/persistImage.test.ts @@ -0,0 +1,29 @@ +import { _persistImage } from '@/services/backend/persistImage'; +import { Client } from 'minio'; +import process from 'process'; + +describe('persist image', () => { + const persistImage = _persistImage( + new Client({ + endPoint: process.env.OS_URL!, + port: Number(process.env.OS_PORT)!, + accessKey: process.env.OS_ACCESS_KEY!, + secretKey: process.env.OS_SECRET_KEY! + }), + process.env.OS_BUCKET_NAME! + ); + console.log(process.env); + it('accessable', async () => { + const result = await persistImage( + 'https://objectstorageapi.dev.sealos.top/612knpkd-test/logo.svg', + 'github/bun' + ); + expect(result).not.toBeNull(); + console.log(result); + }, 10000); + + it('null', async () => { + const result = await persistImage('https://avatars', 'xxx'); + expect(result).toBeNull(); + }, 10000); +}); diff --git a/frontend/desktop/src/api/auth.ts b/frontend/desktop/src/api/auth.ts index 057724f302c..23f01168226 100644 --- a/frontend/desktop/src/api/auth.ts +++ b/frontend/desktop/src/api/auth.ts @@ -1,18 +1,63 @@ import request from '@/services/request'; -import { Session } from 'sealos-desktop-sdk'; -import { TUserExist } from '@/types/user'; -import { ApiResp } from '@/types'; -import { AxiosInstance } from 'axios'; +import {Session} from 'sealos-desktop-sdk'; +import {TUserExist} from '@/types/user'; +import {ApiResp, Region} from '@/types'; +import {AxiosHeaders, AxiosHeaderValue, type AxiosInstance} from 'axios'; +import useSessionStore from '@/stores/session'; +export const _getRegionToken = (request: AxiosInstance) => () => + request.post>('/api/auth/regionToken'); + +export const getRegionToken = _getRegionToken(request); export const _passwordExistRequest = (request: AxiosInstance) => (data: { user: string }) => - request.post>('/api/auth/password/exist', data); + request.post>('/api/auth/password/exist', data); export const _passwordLoginRequest = - (request: AxiosInstance) => (data: { user: string; password: string }) => - request.post>('/api/auth/password', data); + (request: AxiosInstance, switchAuth: (token: string) => void) => + (data: { user: string; password: string, inviterId: string | null | undefined } | { + user: string; + password: string + }) => + request.post>('/api/auth/password', data).then( + ({data}) => { + if (data) { + switchAuth(data.token); + return _getRegionToken(request)(); + } else { + return null; + } + }, + (err) => Promise.resolve(null) + ); export const _passwordModifyRequest = - (request: AxiosInstance) => (data: { oldPassword: string; newPassword: string }) => - request.post>('/api/auth/password/modify', data); - + (request: AxiosInstance) => (data: { oldPassword: string; newPassword: string }) => + request.post>('/api/auth/password/modify', data); +export const _UserInfo = (request: AxiosInstance) => () => + request.post< + any, + ApiResp<{ + info: { + uid: string; + createdAt: Date; + updatedAt: Date; + avatarUri: string; + nickname: string; + id: string; + name: string; + }; + }> + >('/api/auth/info'); +export const _regionList = (request: AxiosInstance) => () => + request.get< + any, + ApiResp<{ + regionList: Region[]; + }> + >('/api/auth/regionList'); export const passwordExistRequest = _passwordExistRequest(request); -export const passwordLoginRequest = _passwordLoginRequest(request); +export const passwordLoginRequest = _passwordLoginRequest(request, (token) => { + useSessionStore.setState({token: token}); +}); export const passwordModifyRequest = _passwordModifyRequest(request); +export const UserInfo = _UserInfo(request); + +export const regionList = _regionList(request); diff --git a/frontend/desktop/src/api/namespace.ts b/frontend/desktop/src/api/namespace.ts index da730ac4db0..4e904578469 100644 --- a/frontend/desktop/src/api/namespace.ts +++ b/frontend/desktop/src/api/namespace.ts @@ -5,8 +5,7 @@ import { TeamUserDto } from '@/types/user'; import { AxiosInstance } from 'axios'; import { Session } from 'sealos-desktop-sdk/*'; export const _abdicateRequest = - (request: AxiosInstance) => - (data: { ns_uid: string; targetUserId: string; targetUsername: string }) => + (request: AxiosInstance) => (data: { ns_uid: string; targetUserCrUid: string }) => request.post>('/api/auth/namespace/abdicate', data); export const _createRequest = @@ -23,12 +22,12 @@ export const _deleteTeamRequest = ns_uid }); export const _inviteMemberRequest = - (request: AxiosInstance) => (props: { ns_uid: string; targetUsername: string; role: UserRole }) => + (request: AxiosInstance) => (props: { ns_uid: string; targetUserId: string; role: UserRole }) => request.post>('/api/auth/namespace/invite', props); export const _modifyRoleRequest = (request: AxiosInstance) => - (props: { ns_uid: string; tUserId: string; tK8s_username: string; tRole: UserRole }) => + (props: { ns_uid: string; targetUserCrUid: string; tRole: UserRole }) => request.post>('/api/auth/namespace/modifyRole', props); export const _nsListRequest = (request: AxiosInstance) => () => @@ -43,7 +42,7 @@ type verifyParam = { ns_uid: string; action: reciveAction }; export const _verifyInviteRequest = (request: AxiosInstance) => (data: verifyParam) => request.post>('/api/auth/namespace/verifyInvite', data); export const _removeMemberRequest = - (request: AxiosInstance) => (data: { ns_uid: string; tUserId: string; tK8s_username: string }) => + (request: AxiosInstance) => (data: { ns_uid: string; targetUserCrUid: string }) => request.post>('/api/auth/namespace/removeUser', data); export const _teamDetailsRequest = (request: AxiosInstance) => (ns_uid: string) => @@ -54,7 +53,7 @@ export const _teamDetailsRequest = (request: AxiosInstance) => (ns_uid: string) export const _reciveMessageRequest = (request: AxiosInstance) => () => request.post>('/api/auth/namespace/recive'); export const _switchRequest = (request: AxiosInstance) => (ns_uid: string) => - request.post>('/api/auth/namespace/switch', { + request.post>('/api/auth/namespace/switch', { ns_uid }); // 提供给prod/dev环境使用 diff --git a/frontend/desktop/src/api/platform.ts b/frontend/desktop/src/api/platform.ts index eabf64f7566..bdb6da57c50 100644 --- a/frontend/desktop/src/api/platform.ts +++ b/frontend/desktop/src/api/platform.ts @@ -50,7 +50,7 @@ export const getWechatQR = () => ); export const getWechatResult = (payload: { code: string }) => - request.get>('/api/auth/publicWechat/getWechatResult', { + request.get>('/api/auth/publicWechat/getWechatResult', { params: payload }); diff --git a/frontend/desktop/src/components/account/index.tsx b/frontend/desktop/src/components/account/index.tsx index 50e6f201921..68d025693ba 100644 --- a/frontend/desktop/src/components/account/index.tsx +++ b/frontend/desktop/src/components/account/index.tsx @@ -14,15 +14,15 @@ import { PopoverContent, PopoverBody, IconButton, - HStack + HStack, + VStack } from '@chakra-ui/react'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useTranslation } from 'next-i18next'; import { useRouter } from 'next/router'; import { useMemo } from 'react'; -import Iconfont from '../iconfont'; import useAppStore from '@/stores/app'; -import { ApiResp } from '@/types'; +import { ApiResp, Region } from '@/types'; import { formatMoney } from '@/utils/format'; import TeamCenter from '@/components/team/TeamCenter'; import NsList from '@/components/team/NsList'; @@ -32,24 +32,40 @@ import PasswordModify from './PasswordModify'; import { useGlobalStore } from '@/stores/global'; import { CopyIcon, DownloadIcon, LogoutIcon, RightArrowIcon } from '@sealos/ui'; import { ImageFallBackUrl } from '@/stores/config'; +import { jwtDecode } from 'jwt-decode'; +import { AccessTokenPayload } from '@/types/token'; const NsMenu = () => { const { t } = useTranslation(); const session = useSessionStore((s) => s.session); + const setToken = useSessionStore((s) => s.setToken); const setSession = useSessionStore((s) => s.setSession); - const { ns_uid } = session.user; + const ns_uid = session?.user?.ns_uid || ''; const router = useRouter(); const mutation = useMutation({ mutationFn: switchRequest, - onSuccess(data) { + async onSuccess(data) { if (data.code === 200 && !!data.data) { - setSession(data.data); - router.reload(); + setToken(data.data.token); + const payload = jwtDecode(data.data.token); + if (session) { + setSession({ + ...session, + user: { + ...session.user, + nsid: payload.workspaceId, + ns_uid: payload.workspaceUid + }, + kubeconfig: data.data.kubeconfig + }); + } else { + throw Error('session in invalid'); + } } } }); const switchTeam = async ({ uid }: { uid: string }) => { - if (ns_uid !== uid) mutation.mutate(uid); + if (ns_uid !== uid) mutation.mutateAsync(uid).then(router.reload); }; const { data } = useQuery({ queryKey: ['teamList', 'teamGroup'], @@ -59,7 +75,7 @@ const NsMenu = () => { const namespace = namespaces.find((x) => x.uid === ns_uid); const defaultNamespace = namespaces.find((x) => x.nstype === NSType.Private); if (!namespace && defaultNamespace && namespaces.length > 0) { - // 被删了 + // will be deleted switchTeam({ uid: defaultNamespace.uid }); } return ( @@ -115,25 +131,19 @@ const NsMenu = () => { }; export default function Account({ disclosure }: { disclosure: UseDisclosureReturn }) { const router = useRouter(); - const { t } = useTranslation(); - const { delSession, getSession } = useSessionStore(); - const { user, kubeconfig } = getSession(); const { copyData } = useCopyData(); - const openApp = useAppStore((s) => s.openApp); const installApp = useAppStore((s) => s.installedApps); - const { ns_uid, nsid, userId, k8s_username } = user || { - ns_uid: '', - nsid: '', - userId: '', - k8s_username: '' - }; + const { t } = useTranslation(); + const { delSession, session, setToken } = useSessionStore(); + const user = session?.user; const { data } = useQuery({ - queryKey: ['getAccount', { ns_uid, userId, k8s_username }], + queryKey: ['getAmount', { userId: user?.userCrUid }], queryFn: () => - request>( + request>( '/api/account/getAmount' - ) + ), + enabled: !!user }); const balance = useMemo(() => { let real_balance = data?.data?.balance || 0; @@ -143,11 +153,18 @@ export default function Account({ disclosure }: { disclosure: UseDisclosureRetur return real_balance; }, [data]); const queryclient = useQueryClient(); + const kubeconfigQuery = useQuery({ + queryKey: [user, 'kubeconfig'], + queryFn: () => request.get>('/api/auth/getKubeconfig'), + enabled: !!user + }); + const kubeconfig = kubeconfigQuery.data?.data?.kubeconfig; const logout = (e: React.MouseEvent) => { e.preventDefault(); delSession(); queryclient.clear(); router.replace('/signin'); + setToken(''); }; const needPassword = useGlobalStore((s) => s.needPassword); const rechargeEnabled = useGlobalStore((s) => s.rechargeEnabled); @@ -193,96 +210,98 @@ export default function Account({ disclosure }: { disclosure: UseDisclosureRetur - ID: {nsid} + ID: {user?.userId || ''} copyData(nsid)} + onClick={() => copyData(user?.userId || '')} icon={} aria-label={'copy nsid'} /> - - - - {t('Manage Team')} - - - {needPassword && ( - - {t('changePassword')} - + + {/**/} + + + + {t('Manage Team')} + - )} - - - {t('Balance')}: {formatMoney(balance).toFixed(2)} - - {rechargeEnabled && ( - { - const costcenter = installApp.find((t) => t.key === 'system-costcenter'); - if (!costcenter) return; - openApp(costcenter, { - query: { - openRecharge: 'true' - } - }); - disclosure.onClose(); - }} - _hover={{ - bgColor: 'rgba(0, 0, 0, 0.03)' - }} - transition={'0.3s'} - p="4px" - color={'#219BF4'} - fontWeight="500" - fontSize="12px" - cursor={'pointer'} + {needPassword && ( + - {t('Charge')} - + {t('changePassword')} + + )} - - { - - kubeconfig - - download('kubeconfig.yaml', kubeconfig)} - icon={} - aria-label={'Download kc'} - /> - copyData(kubeconfig)} - icon={} - aria-label={'copy kc'} - /> + + + {t('Balance')}: {formatMoney(balance).toFixed(2)} + + {rechargeEnabled && ( + { + const costcenter = installApp.find((t) => t.key === 'system-costcenter'); + if (!costcenter) return; + openApp(costcenter, { + query: { + openRecharge: 'true' + } + }); + disclosure.onClose(); + }} + _hover={{ + bgColor: 'rgba(0, 0, 0, 0.03)' + }} + transition={'0.3s'} + p="4px" + color={'#219BF4'} + fontWeight="500" + fontSize="12px" + cursor={'pointer'} + > + {t('Charge')} + + )} - } - + { + + kubeconfig + + kubeconfig && download('kubeconfig.yaml', kubeconfig)} + icon={} + aria-label={'Download kc'} + /> + kubeconfig && copyData(kubeconfig)} + icon={} + aria-label={'copy kc'} + /> + + } + + diff --git a/frontend/desktop/src/components/desktop_content/time.tsx b/frontend/desktop/src/components/desktop_content/time.tsx index 35b75c01ad9..a475170b310 100644 --- a/frontend/desktop/src/components/desktop_content/time.tsx +++ b/frontend/desktop/src/components/desktop_content/time.tsx @@ -1,7 +1,7 @@ import { formatTime } from '@/utils/tools'; import { Flex, Text } from '@chakra-ui/react'; import { useTranslation } from 'next-i18next'; -import { useEffect, useMemo, useState } from 'react'; +import { useEffect, useLayoutEffect, useMemo, useState } from 'react'; const WeekDay = { Sunday: '周日', @@ -25,7 +25,9 @@ export default function TimeComponent(props: any) { clearInterval(timer); }; }, []); - + // useLayoutEffect(()=>{ + // setTime(new Date()) + // },[]) const day = useMemo(() => { try { const temp = formatTime(time, 'dddd') as keyof typeof WeekDay; diff --git a/frontend/desktop/src/components/region/RegionToggle.tsx b/frontend/desktop/src/components/region/RegionToggle.tsx new file mode 100644 index 00000000000..428960910b4 --- /dev/null +++ b/frontend/desktop/src/components/region/RegionToggle.tsx @@ -0,0 +1,144 @@ +import request from '@/services/request'; +import { Box, Divider, HStack, Text, useDisclosure } from '@chakra-ui/react'; +import { ExchangeIcon, InfoIcon, ProviderIcon } from '@sealos/ui'; +import { useQuery } from '@tanstack/react-query'; +import { useTranslation } from 'next-i18next'; +import { useMemo } from 'react'; +import useSessionStore from '@/stores/session'; +import { useRouter } from 'next/router'; +import { regionList as getRegionList } from '@/api/auth'; +import { ApiResp, Region } from '@/types'; +import { jwtDecode } from 'jwt-decode'; +import { AccessTokenPayload } from '@/types/token'; + +export default function RegionToggle() { + const disclosure = useDisclosure(); + const { t, i18n } = useTranslation(); + const { t: providerT } = useTranslation('cloudProviders'); + const router = useRouter(); + const { data, isSuccess } = useQuery(['regionlist'], getRegionList); + const regionList = useMemo(() => data?.data?.regionList || [], [data]); + const token = useSessionStore((s) => s.token); + const curRegionUid = useMemo(() => { + try { + return jwtDecode(token).regionUid; + } catch { + return undefined; + } + }, [token]); + const curRegion = regionList.find((r) => r.uid === curRegionUid); + const handleCick = async (region: Region) => { + const target = new URL(`https://${region.domain}/switchRegion`); + const res = await request.get>('/api/auth/globalToken'); + const token = res?.data?.token; + if (!token) return; + target.searchParams.append('token', token); + await router.replace(target); + }; + return ( + <> + + disclosure.onOpen()} + > + + {providerT(curRegion?.location || '')} {curRegion?.description.serial} + {' '} + + + {disclosure.isOpen ? ( + <> + disclosure.onClose()} + > + + + + {regionList.map((region) => { + const cpuPrice = region.description.prices.find((p) => p.name === 'CPU'); + return ( + + + + {providerT(region.location)} {region.description.serial} + + {cpuPrice && ( + + {cpuPrice?.name} {cpuPrice?.unit_price || 0} {t('Yuan')}/{t('Core')}/ + {t('Year')} + + )} + + + + + + {providerT('Provider')} + + + {providerT(region.description.provider)} + + + + {t('Description')} + + + {' '} + {t(region.description.description.zh)} + + + + ); + })} + + + + + ) : null} + + + ); +} diff --git a/frontend/desktop/src/components/signin/auth/AuthList.tsx b/frontend/desktop/src/components/signin/auth/AuthList.tsx new file mode 100644 index 00000000000..4685c18f93f --- /dev/null +++ b/frontend/desktop/src/components/signin/auth/AuthList.tsx @@ -0,0 +1,132 @@ +import request from '@/services/request'; +import useSessionStore from '@/stores/session'; +import { ApiResp, SystemEnv } from '@/types'; +import { OauthProvider } from '@/types/user'; +import { Button, Flex, Icon } from '@chakra-ui/react'; +import { GithubIcon, GoogleIcon, WechatIcon } from '@sealos/ui'; +import { useQuery } from '@tanstack/react-query'; +import { MouseEventHandler } from 'react'; +import { useRouter } from 'next/router'; +const AuthList = () => { + const { data: platformEnv } = useQuery(['getPlatformEnv'], () => + request>('/api/platform/getEnv') + ); + const { + needGithub = false, + needWechat = false, + needGoogle = false, + wechat_client_id = '', + github_client_id = '', + google_client_id = '', + callback_url = '', + // https://sealos.io/siginIn + oauth_proxy = '' + } = platformEnv?.data ?? {}; + const oauthLogin = async ({ url, provider }: { url: string; provider?: OauthProvider }) => { + setProvider(provider); + window.location.href = url; + }; + const router = useRouter(); + const oauthProxyLogin = async ({ + state, + provider, + id + }: { + state: string; + provider: OauthProvider; + id: string; + }) => { + setProvider(provider); + const target = new URL(oauth_proxy); + const callback = new URL(callback_url); + callback.searchParams.append('state', state); + target.searchParams.append('oauthProxyState', callback.toString()); + target.searchParams.append('oauthProxyClientID', id); + target.searchParams.append('oauthProxyProvider', provider); + router.replace(target.toString()); + }; + const { generateState, setProvider } = useSessionStore(); + const authList: { icon: typeof Icon; cb: MouseEventHandler; need: boolean }[] = [ + { + icon: GithubIcon, + cb: (e) => { + e.preventDefault(); + const state = generateState(); + if (oauth_proxy) + oauthProxyLogin({ + provider: 'github', + state, + id: github_client_id + }); + else + oauthLogin({ + provider: 'github', + url: `https://github.com/login/oauth/authorize?client_id=${github_client_id}&redirect_uri=${callback_url}&scope=user:email%20read:user&state=${state}` + }); + }, + need: needGithub + }, + { + icon: WechatIcon, + cb: (e) => { + e.preventDefault(); + const state = generateState(); + if (oauth_proxy) + oauthProxyLogin({ + provider: 'wechat', + state, + id: wechat_client_id + }); + else + oauthLogin({ + provider: 'wechat', + url: `https://open.weixin.qq.com/connect/qrconnect?appid=${wechat_client_id}&redirect_uri=${callback_url}&response_type=code&state=${state}&scope=snsapi_login&#wechat_redirect` + }); + }, + need: needWechat + }, + { + icon: GoogleIcon, + cb: (e) => { + e.preventDefault(); + const state = generateState(); + const scope = encodeURIComponent(`https://www.googleapis.com/auth/userinfo.profile openid`); + if (oauth_proxy) + oauthProxyLogin({ + state, + provider: 'google', + id: google_client_id + }); + else + oauthLogin({ + provider: 'google', + url: `https://accounts.google.com/o/oauth2/v2/auth?client_id=${google_client_id}&redirect_uri=${callback_url}&response_type=code&state=${state}&scope=${scope}&include_granted_scopes=true` + }); + }, + need: needGoogle + } + ]; + + return ( + + {authList + .filter((item) => item.need) + .map((item, index) => ( + + ))} + + ); +}; + +export default AuthList; diff --git a/frontend/desktop/src/components/signin/auth/useAuthList.tsx b/frontend/desktop/src/components/signin/auth/useAuthList.tsx deleted file mode 100644 index b8c4e809e73..00000000000 --- a/frontend/desktop/src/components/signin/auth/useAuthList.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import request from '@/services/request'; -import useSessionStore from '@/stores/session'; -import { ApiResp, SystemEnv } from '@/types'; -import { OauthProvider } from '@/types/user'; -import { Button, Flex, Icon, Image } from '@chakra-ui/react'; -import { GithubIcon, GoogleIcon, WechatIcon } from '@sealos/ui'; -import { useQuery } from '@tanstack/react-query'; -import { MouseEventHandler } from 'react'; - -const useAuthList = () => { - const { data: platformEnv } = useQuery(['getPlatformEnv'], () => - request>('/api/platform/getEnv') - ); - const { - needGithub = false, - needWechat = false, - needGoogle = false, - wechat_client_id = '', - github_client_id = '', - google_client_id = '', - callback_url = '' - } = platformEnv?.data ?? {}; - const { generateState, setProvider } = useSessionStore(); - - const oauthLogin = async ({ url, provider }: { url: string; provider?: OauthProvider }) => { - setProvider(provider); - window.location.href = url; - }; - - const authList: { icon: typeof Icon; cb: MouseEventHandler; need: boolean }[] = [ - { - icon: GithubIcon, - cb: (e) => { - e.preventDefault(); - const state = generateState(); - oauthLogin({ - provider: 'github', - url: `https://github.com/login/oauth/authorize?client_id=${github_client_id}&redirect_uri=${callback_url}&scope=user:email%20read:user&state=${state}` - }); - }, - need: needGithub - }, - { - icon: WechatIcon, - cb: (e) => { - e.preventDefault(); - const state = generateState(); - oauthLogin({ - provider: 'wechat', - url: `https://open.weixin.qq.com/connect/qrconnect?appid=${wechat_client_id}&redirect_uri=${callback_url}&response_type=code&state=${state}&scope=snsapi_login&#wechat_redirect` - }); - }, - need: needWechat - }, - { - icon: GoogleIcon, - cb: (e) => { - e.preventDefault(); - const state = generateState(); - const scope = encodeURIComponent(`https://www.googleapis.com/auth/userinfo.profile openid`); - oauthLogin({ - provider: 'google', - //https://www.googleapis.com/auth/userinfo.profile - url: `https://accounts.google.com/o/oauth2/v2/auth?client_id=${google_client_id}&redirect_uri=${callback_url}&response_type=code&state=${state}&scope=${scope}&include_granted_scopes=true` - }); - }, - need: needGoogle - } - ]; - - const AuthList = () => { - return ( - - {authList - .filter((item) => item.need) - .map((item, index) => ( - - ))} - - ); - }; - - return { - AuthList - }; -}; - -export default useAuthList; diff --git a/frontend/desktop/src/components/signin/auth/useLanguage.tsx b/frontend/desktop/src/components/signin/auth/useLanguage.tsx index 2a953dec0dc..56b3a0bc1f6 100644 --- a/frontend/desktop/src/components/signin/auth/useLanguage.tsx +++ b/frontend/desktop/src/components/signin/auth/useLanguage.tsx @@ -1,6 +1,5 @@ -import LangSelect from '@/components/LangSelect'; import LangSelectSimple from '@/components/LangSelect/simple'; -import { Box, Flex, UseDisclosureReturn } from '@chakra-ui/react'; +import { Flex, UseDisclosureReturn } from '@chakra-ui/react'; import { I18n } from 'next-i18next'; type LanguageType = { disclosure: UseDisclosureReturn; i18n: I18n | null }; diff --git a/frontend/desktop/src/components/signin/auth/usePassword.tsx b/frontend/desktop/src/components/signin/auth/usePassword.tsx index 0839ebec1d2..facc72764ef 100644 --- a/frontend/desktop/src/components/signin/auth/usePassword.tsx +++ b/frontend/desktop/src/components/signin/auth/usePassword.tsx @@ -1,14 +1,13 @@ -import { passwordExistRequest, passwordLoginRequest } from '@/api/auth'; -import request from '@/services/request'; +import { passwordExistRequest, passwordLoginRequest, UserInfo } from '@/api/auth'; import useSessionStore from '@/stores/session'; -import { ApiResp, Session } from '@/types'; -import { TUserExist } from '@/types/user'; import { Flex, Image, Img, Input, InputGroup, InputLeftAddon, Text } from '@chakra-ui/react'; import { useTranslation } from 'next-i18next'; import { useRouter } from 'next/router'; import lockIcon from 'public/images/lock.svg'; import { useState } from 'react'; import { useForm } from 'react-hook-form'; +import { jwtDecode } from 'jwt-decode'; +import { AccessTokenPayload } from '@/types/token'; export default function usePassword({ showError @@ -22,8 +21,8 @@ export default function usePassword({ // 对于注册的用户,需要先验证密码 0 默认页面;1为验证密码页面 const [pageState, setPageState] = useState(0); + const setToken = useSessionStore((s) => s.setToken); const setSession = useSessionStore((s) => s.setSession); - const { register, handleSubmit, watch, trigger, getValues } = useForm<{ username: string; password: string; @@ -39,36 +38,76 @@ export default function usePassword({ return deepSearch(Object.values(obj)[0]); }; - handleSubmit( + await handleSubmit( async (data) => { if (data?.username && data?.password) { try { setIsLoading(true); const inviterId = localStorage.getItem('inviterId'); const result = await passwordExistRequest({ user: data.username }); + if (result?.code === 200) { const result = await passwordLoginRequest({ user: data.username, - password: data.password + password: data.password, + inviterId }); - setSession(result.data!); - router.replace('/'); + if (!!result?.data) { + const regionUserToken = result.data.token; + setToken(regionUserToken); + const infoData = await UserInfo(); + const payload = jwtDecode(regionUserToken); + setSession({ + token: regionUserToken, + user: { + k8s_username: payload.userCrName, + name: infoData.data?.info.nickname || '', + avatar: infoData.data?.info.avatarUri || '', + nsid: payload.workspaceId, + ns_uid: payload.workspaceUid, + userCrUid: payload.userCrUid, + userUid: payload.userUid, + userId: payload.userId + }, + // @ts-ignore + kubeconfig: result.data.kubeconfig + }); + await router.replace('/'); + } return; - } - if (result?.code === 201) { + } else if (result?.code === 201) { setUserExist(!!result?.data?.exist); setPageState(1); if (!!data?.confimPassword) { if (data?.password !== data?.confimPassword) { showError('password not match'); } else { - const result = await request.post>('/api/auth/password', { + const regionResult = await passwordLoginRequest({ user: data.username, password: data.password, inviterId }); - setSession(result.data!); - router.replace('/'); + if (!!regionResult?.data) { + setToken(regionResult.data.token); + const infoData = await UserInfo(); + const payload = jwtDecode(regionResult.data.token); + setSession({ + token: regionResult.data.token, + user: { + k8s_username: payload.userCrName, + name: infoData.data?.info.nickname || '', + avatar: infoData.data?.info.avatarUri || '', + nsid: payload.workspaceId, + ns_uid: payload.workspaceUid, + userCrUid: payload.userCrUid, + userId: payload.userId, + userUid: payload.userUid + }, + // @ts-ignore + kubeconfig: result.data.kubeconfig + }); + await router.replace('/'); + } } } } diff --git a/frontend/desktop/src/components/signin/auth/useSms.tsx b/frontend/desktop/src/components/signin/auth/useSms.tsx index 9af19f65a13..90054ded19e 100644 --- a/frontend/desktop/src/components/signin/auth/useSms.tsx +++ b/frontend/desktop/src/components/signin/auth/useSms.tsx @@ -15,6 +15,10 @@ import NextLink from 'next/link'; import { useRouter } from 'next/router'; import { MouseEventHandler, useEffect, useRef, useState } from 'react'; import { useForm } from 'react-hook-form'; +import { getRegionToken, UserInfo } from '@/api/auth'; +import { jwtDecode } from 'jwt-decode'; +import { uploadConvertData } from '@/api/platform'; +import { AccessTokenPayload } from '@/types/token'; export default function useSms({ showError @@ -24,9 +28,9 @@ export default function useSms({ const { t } = useTranslation(); const _remainTime = useRef(0); const router = useRouter(); - const { setSession } = useSessionStore(); + const setSession = useSessionStore((s) => s.setSession); const [isLoading, setIsLoading] = useState(false); - + const setToken = useSessionStore((s) => s.setToken); const { register, handleSubmit, trigger, getValues } = useForm<{ phoneNumber: string; verifyCode: string; @@ -41,18 +45,50 @@ export default function useSms({ return deepSearch(Object.values(obj)[0]); }; - handleSubmit( + await handleSubmit( async (data) => { try { setIsLoading(true); - const inviterId = localStorage.getItem('inviterId'); - const result = await request.post>('/api/auth/phone/verify', { - phoneNumbers: data.phoneNumber, - code: data.verifyCode, - inviterId - }); - setSession(result.data!); - router.replace('/'); + const result1 = await request.post>( + '/api/auth/phone/verify', + { + phoneNumbers: data.phoneNumber, + code: data.verifyCode + } + ); + const globalToken = result1?.data?.token; + if (!globalToken) throw Error(); + setToken(globalToken); + const regionTokenRes = await getRegionToken(); + if (regionTokenRes?.data) { + const regionUserToken = regionTokenRes.data.token; + setToken(regionUserToken); + const infoData = await UserInfo(); + const payload = jwtDecode(regionUserToken); + setSession({ + token: regionUserToken, + user: { + k8s_username: payload.userCrName, + name: infoData.data?.info.nickname || '', + avatar: infoData.data?.info.avatarUri || '', + nsid: payload.workspaceId, + ns_uid: payload.workspaceUid, + userCrUid: payload.userCrUid, + userId: payload.userId, + userUid: payload.userUid + }, + kubeconfig: regionTokenRes.data.kubeconfig + }); + uploadConvertData([3]).then( + (res) => { + console.log(res); + }, + (err) => { + console.log(err); + } + ); + await router.replace('/'); + } } catch (error) { showError(t('Invalid verification code') || 'Invalid verification code'); } finally { diff --git a/frontend/desktop/src/components/signin/auth/useWechat.tsx b/frontend/desktop/src/components/signin/auth/useWechat.tsx index b0d56b08fa1..413da4e1a7c 100644 --- a/frontend/desktop/src/components/signin/auth/useWechat.tsx +++ b/frontend/desktop/src/components/signin/auth/useWechat.tsx @@ -1,55 +1,78 @@ -import { getWechatQR, getWechatResult } from '@/api/platform'; +import {getWechatQR, getWechatResult} from '@/api/platform'; import useSessionStore from '@/stores/session'; -import { Box, Image, useToast } from '@chakra-ui/react'; -import { useQuery } from '@tanstack/react-query'; -import { useTranslation } from 'next-i18next'; -import { useRouter } from 'next/router'; -import React, { useCallback } from 'react'; +import {Box, Image, useToast} from '@chakra-ui/react'; +import {useQuery} from '@tanstack/react-query'; +import {useTranslation} from 'next-i18next'; +import {useRouter} from 'next/router'; +import React, {useCallback, useEffect} from 'react'; +import {UserInfo} from "@/api/auth"; +import {jwtDecode} from "jwt-decode"; +import {AccessTokenPayload} from "@/types/token"; export default function useWechat() { - const { t } = useTranslation(); - const router = useRouter(); - const { setSession } = useSessionStore(); + const {t} = useTranslation(); + const router = useRouter(); + const {setSession, setToken} = useSessionStore(); - const [wechatInfo, setwechatInfo] = React.useState<{ - code: string; - codeUrl: string; - }>(); + const [wechatInfo, setwechatInfo] = React.useState<{ + code: string; + codeUrl: string; + }>(); - const login = React.useCallback(() => { - getWechatQR().then((res) => { - console.log(res); - setwechatInfo(res.data); - }); - }, []); + const login = React.useCallback(() => { + getWechatQR().then((res) => { + console.log(res); + setwechatInfo(res.data); + }); + }, []); - useQuery( - ['getWechatResult', wechatInfo?.code], - () => getWechatResult({ code: wechatInfo?.code || '' }), - { - refetchInterval: 3 * 1000, - enabled: !!wechatInfo?.code, - onSuccess(data) { - console.log(data); - if (data.code === 200 && data.data) { - setSession(data.data); - router.replace('/'); + const {data, isSuccess} = useQuery( + ['getWechatResult', wechatInfo?.code], + () => getWechatResult({code: wechatInfo?.code || ''}), + { + refetchInterval: 3 * 1000, + enabled: !!wechatInfo?.code, } - } - } - ); + ); + useEffect(() => { + (async () => { + if (isSuccess && data && data.code === 200 && data.data) { + const regionUserToken = data.data.token; + setToken(regionUserToken); + const infoData = await UserInfo(); + const payload = jwtDecode(regionUserToken); + setSession({ + token: regionUserToken, + user: { + k8s_username: payload.userCrName, + name: infoData.data?.info.nickname || '', + avatar: infoData.data?.info.avatarUri || '', + nsid: payload.workspaceId, + ns_uid: payload.workspaceUid, + userCrUid: payload.userCrUid, + userUid: payload.userUid, + userId: payload.userId + }, + // @ts-ignore + kubeconfig: result.data.kubeconfig + }); + return router.replace('/'); + } + })() + }, [data, isSuccess]); - const WechatComponent = useCallback( - () => ( - - {wechatInfo?.codeUrl && qrcode} - - ), - [wechatInfo?.codeUrl] - ); - return { - login, - WechatComponent - }; + const WechatComponent = useCallback( + () => ( + + {wechatInfo?.codeUrl && qrcode} + + ), + [wechatInfo?.codeUrl] + ); + + return { + login, + WechatComponent + }; } diff --git a/frontend/desktop/src/components/signin/index.tsx b/frontend/desktop/src/components/signin/index.tsx index 237fe8e96c3..70b6d0df174 100644 --- a/frontend/desktop/src/components/signin/index.tsx +++ b/frontend/desktop/src/components/signin/index.tsx @@ -1,234 +1,237 @@ -import { getSystemEnv, uploadConvertData } from '@/api/platform'; -import useAuthList from '@/components/signin/auth/useAuthList'; +import AuthList from '@/components/signin/auth/AuthList'; +import {getSystemEnv, uploadConvertData} from '@/api/platform'; import useCustomError from '@/components/signin/auth/useCustomError'; import Language from '@/components/signin/auth/useLanguage'; import usePassword from '@/components/signin/auth/usePassword'; import useProtocol from '@/components/signin/auth/useProtocol'; import useSms from '@/components/signin/auth/useSms'; -import { BackgroundImageUrl, useSystemConfigStore } from '@/stores/config'; +import {BackgroundImageUrl, useSystemConfigStore} from '@/stores/config'; import useSessionStore from '@/stores/session'; -import { LoginType } from '@/types'; +import {LoginType} from '@/types'; import { - Box, - Button, - Flex, - Tab, - TabIndicator, - TabList, - Tabs, - Text, - useDisclosure + Box, + Button, + Flex, + Tab, + TabIndicator, + TabList, + Tabs, + Text, + useDisclosure } from '@chakra-ui/react'; -import { useQuery } from '@tanstack/react-query'; -import { debounce } from 'lodash'; -import { useTranslation } from 'next-i18next'; +import {useQuery, useQueryClient} from '@tanstack/react-query'; +import {debounce} from 'lodash'; +import {useTranslation} from 'next-i18next'; import Head from 'next/head'; -import { useRouter } from 'next/router'; -import { useEffect, useMemo, useState } from 'react'; +import {useRouter} from 'next/router'; +import {useEffect, useMemo, useState} from 'react'; import useWechat from './auth/useWechat'; export default function SigninComponent() { - const { data: platformEnv } = useQuery(['getPlatformEnv'], getSystemEnv); - const { systemConfig } = useSystemConfigStore(); - const { - service_protocol_zh = '', - private_protocol_zh = '', - service_protocol_en = '', - private_protocol_en = '', - needPassword = false, - needSms = false, - openWechatEnabled = false - } = platformEnv?.data || {}; - const needTabs = (needPassword && needSms) || (needSms && openWechatEnabled); + const {data: platformEnv} = useQuery(['getPlatformEnv'], getSystemEnv); + const {systemConfig} = useSystemConfigStore(); + const { + service_protocol_zh = '', + private_protocol_zh = '', + service_protocol_en = '', + private_protocol_en = '', + needPassword = false, + needSms = false, + openWechatEnabled = false + } = platformEnv?.data || {}; + const needTabs = (needPassword && needSms); + const disclosure = useDisclosure(); + const {t, i18n} = useTranslation(); + const [tabIndex, setTabIndex] = useState(LoginType.NONE); - const disclosure = useDisclosure(); - const { t, i18n } = useTranslation(); - const [tabIndex, setTabIndex] = useState(LoginType.NONE); - - const { ErrorComponent, showError } = useCustomError(); - let protocol_data: Parameters[0]; - if (['zh', 'zh-Hans'].includes(i18n.language)) - protocol_data = { - service_protocol: service_protocol_zh, - private_protocol: private_protocol_zh - }; - else - protocol_data = { - service_protocol: service_protocol_en, - private_protocol: private_protocol_en - }; - - const { Protocol, isAgree, setIsInvalid } = useProtocol(protocol_data!); - const { WechatComponent, login: wechatSubmit } = useWechat(); - const { SmsModal, login: smsSubmit, isLoading: smsLoading } = useSms({ showError }); - const { - PasswordComponent, - pageState, - login: passwordSubmit, - isLoading: passwordLoading - } = usePassword({ showError }); - const isLoading = useMemo(() => passwordLoading || smsLoading, [passwordLoading, smsLoading]); - const isSignIn = useSessionStore((s) => s.isUserLogin); - const router = useRouter(); - useEffect(() => { - if (isSignIn()) { - router.replace('/'); - } - }, []); - const { AuthList } = useAuthList(); - - const loginConfig = useMemo(() => { - return { - [LoginType.SMS]: { - login: smsSubmit, - component: - }, - [LoginType.PASSWORD]: { + const {ErrorComponent, showError} = useCustomError(); + let protocol_data: Parameters[0]; + if (['zh', 'zh-Hans'].includes(i18n.language)) + protocol_data = { + service_protocol: service_protocol_zh, + private_protocol: private_protocol_zh + }; + else + protocol_data = { + service_protocol: service_protocol_en, + private_protocol: private_protocol_en + }; + const {Protocol, isAgree, setIsInvalid} = useProtocol(protocol_data!); + const {WechatComponent, login: wechatSubmit} = useWechat(); + const {SmsModal, login: smsSubmit, isLoading: smsLoading} = useSms({showError}); + const { + PasswordComponent, + pageState, login: passwordSubmit, - component: - }, - [LoginType.WeChat]: { - login: wechatSubmit, - component: - }, - [LoginType.NONE]: null - }; - }, [PasswordComponent, SmsModal, WechatComponent, passwordSubmit, smsSubmit, wechatSubmit]); + isLoading: passwordLoading + } = usePassword({showError}); + const isLoading = useMemo(() => passwordLoading || smsLoading, [passwordLoading, smsLoading]); + const isSignIn = useSessionStore((s) => s.isUserLogin); + const delSession = useSessionStore((s) => s.delSession); + const setToken = useSessionStore((s) => s.setToken); + const router = useRouter(); + const queryClient = useQueryClient(); + useEffect(() => { + if (isSignIn()) { + router.replace('/'); + } else { + queryClient.clear(); + delSession(); + setToken(''); + } + }, []); - useEffect(() => { - setTabIndex(needSms ? LoginType.SMS : needPassword ? LoginType.PASSWORD : LoginType.NONE); - }, [needPassword, needSms]); + const loginConfig = useMemo(() => { + return { + [LoginType.SMS]: { + login: smsSubmit, + component: + }, + [LoginType.PASSWORD]: { + login: passwordSubmit, + component: + }, + [LoginType.WeChat]: { + login: wechatSubmit, + component: + }, + [LoginType.NONE]: null + }; + }, [PasswordComponent, SmsModal, WechatComponent, passwordSubmit, smsSubmit, wechatSubmit]); - const LoginComponent = useMemo( - () => (tabIndex !== LoginType.NONE ? loginConfig[tabIndex].component : null), - [loginConfig, tabIndex] - ); + useEffect(() => { + setTabIndex(needSms ? LoginType.SMS : needPassword ? LoginType.PASSWORD : LoginType.NONE); + }, [needPassword, needSms]); - const handleLogin = debounce(() => { - const selectedConfig = loginConfig[tabIndex]; - if (isAgree && selectedConfig) { - const { login } = selectedConfig; - login(); - uploadConvertData([3]) - .then((res) => { - console.log(res); - }) - .catch((err) => { - console.log(err); - }); - } else { - setIsInvalid(true); - showError(t('Read and agree')); - } - }, 500); + const LoginComponent = useMemo( + () => (tabIndex !== LoginType.NONE ? loginConfig[tabIndex].component : null), + [loginConfig, tabIndex] + ); - return ( - - - {systemConfig?.metaTitle} - - - - - - {systemConfig?.title} - - - { + const selectedConfig = loginConfig[tabIndex]; + if (isAgree && selectedConfig) { + const {login} = selectedConfig; + login() + uploadConvertData([3]).then((res) => { + console.log(res); + }) + .catch((err) => { + console.log(err); + }); + } else { + setIsInvalid(true); + showError(t('Read and agree')); + } + }, 500); + + return ( + - + + {systemConfig?.metaTitle} + + + + + + {systemConfig?.title} + + + + - {pageState === 0 && needTabs && ( - { - if (idx === LoginType.WeChat) { - wechatSubmit(); - } - setTabIndex(idx); - }} - variant="unstyled" - p={'0'} - width={'full'} - > - - - {t('Verification Code Login')} - - - {t('Password Login')} - - {openWechatEnabled && ( - - {t('Official account login')} - - )} - - - - )} + {pageState === 0 && needTabs && ( + { + if (idx === LoginType.WeChat) { + wechatSubmit(); + } + setTabIndex(idx); + }} + variant="unstyled" + p={'0'} + width={'full'} + > + + + {t('Verification Code Login')} + + + {t('Password Login')} + + {openWechatEnabled && ( + + {t('Official account login')} + + )} + + + + )} - {LoginComponent} + {LoginComponent} - {tabIndex !== LoginType.WeChat && ( - <> - - - - - )} - - - - - ); + {tabIndex !== LoginType.WeChat && ( + <> + + + + + )} + + + + + ); } diff --git a/frontend/desktop/src/components/team/Abdication.tsx b/frontend/desktop/src/components/team/Abdication.tsx index 2a586cbacb1..81901ea4e4d 100644 --- a/frontend/desktop/src/components/team/Abdication.tsx +++ b/frontend/desktop/src/components/team/Abdication.tsx @@ -25,6 +25,7 @@ import { useCustomToast } from '@/hooks/useCustomToast'; import { ApiResp } from '@/types'; import { useTranslation } from 'next-i18next'; import { ExchangeIcon, ExpanMoreIcon } from '@sealos/ui'; + export default function Abdication({ ns_uid, users, @@ -38,10 +39,11 @@ export default function Abdication({ const queryClient = useQueryClient(); const defaultUser = users[0]; const [targetUser, setTargetUser] = useState({ - name: defaultUser?.name || '', + name: defaultUser?.nickname || '', avatar: defaultUser?.avatarUrl || '', uid: defaultUser?.uid || '', - k8s_username: defaultUser?.k8s_username || '' + k8s_username: defaultUser?.k8s_username || '', + crUid: defaultUser?.crUid || '' }); const { toast } = useCustomToast({ status: 'error' }); const mutation = useMutation({ @@ -60,8 +62,7 @@ export default function Abdication({ const submit = () => { mutation.mutate({ ns_uid, - targetUserId: targetUser.uid, - targetUsername: targetUser.k8s_username + targetUserCrUid: targetUser.crUid }); }; return ( @@ -126,10 +127,11 @@ export default function Abdication({ onClick={(e) => { e.preventDefault(); setTargetUser({ - name: user.name, + name: user.nickname, avatar: user.avatarUrl, k8s_username: user.k8s_username, - uid: user.uid + uid: user.uid, + crUid: user.crUid }); }} key={user.uid} @@ -142,7 +144,7 @@ export default function Abdication({ borderRadius={'50%'} mr="8px" /> - {user.name} + {user.nickname} ))} diff --git a/frontend/desktop/src/components/team/CreateTeam.tsx b/frontend/desktop/src/components/team/CreateTeam.tsx index 68c448860e6..80a244028e8 100644 --- a/frontend/desktop/src/components/team/CreateTeam.tsx +++ b/frontend/desktop/src/components/team/CreateTeam.tsx @@ -27,11 +27,11 @@ export default function CreateTeam({ textButton = false }: { textButton?: boolea const { t } = useTranslation(); const [teamName, setTeamName] = useState(''); const session = useSessionStore((s) => s.session); - const userId = session.user.userId; + const userCrUid = session?.user?.userCrUid; const queryClient = useQueryClient(); const { toast } = useCustomToast({ status: 'error' }); const mutation = useMutation(createRequest, { - mutationKey: [{ teamName, userId }], + mutationKey: [{ teamName, userCrUid }], onSuccess(data) { if (data.code === 200) { queryClient.invalidateQueries({ queryKey: ['teamList'] }); diff --git a/frontend/desktop/src/components/team/DissolveTeam.tsx b/frontend/desktop/src/components/team/DissolveTeam.tsx index ee20fbc91ce..e51d6b7f1f5 100644 --- a/frontend/desktop/src/components/team/DissolveTeam.tsx +++ b/frontend/desktop/src/components/team/DissolveTeam.tsx @@ -65,7 +65,7 @@ export default function DissolveTeam({ <> - - - )} - - - - - ); + {tabIndex !== LoginType.WeChat && ( + <> + + + + + )} + + + + + ); } diff --git a/frontend/desktop/src/components/user_menu/index.tsx b/frontend/desktop/src/components/user_menu/index.tsx index 27af617f17c..f4ad2dab642 100644 --- a/frontend/desktop/src/components/user_menu/index.tsx +++ b/frontend/desktop/src/components/user_menu/index.tsx @@ -1,113 +1,113 @@ import Account from '@/components/account'; import Notification from '@/components/notification'; import useSessionStore from '@/stores/session'; -import {Box, Center, Flex, FlexProps, Image, useDisclosure} from '@chakra-ui/react'; +import { Box, Center, Flex, FlexProps, Image, useDisclosure } from '@chakra-ui/react'; import LangSelectSimple from '../LangSelect/simple'; import Iconfont from '../iconfont'; import GithubComponent from './github'; -import {ImageFallBackUrl, useSystemConfigStore} from '@/stores/config'; -import {ReactElement, useState} from 'react'; +import { ImageFallBackUrl, useSystemConfigStore } from '@/stores/config'; +import { ReactElement, useState } from 'react'; import RegionToggle from '@/components/region/RegionToggle'; enum UserMenuKeys { - LangSelect, - Notification, - Account, - Region + LangSelect, + Notification, + Account, + Region } export default function Index(props: { userMenuStyleProps?: FlexProps }) { - const [notificationAmount, setNotificationAmount] = useState(0); - const accountDisclosure = useDisclosure(); - const showDisclosure = useDisclosure(); + const [notificationAmount, setNotificationAmount] = useState(0); + const accountDisclosure = useDisclosure(); + const showDisclosure = useDisclosure(); - const {systemConfig} = useSystemConfigStore(); - const userInfo = useSessionStore((state) => state.session); - const { - userMenuStyleProps = { - alignItems: 'center', - position: 'absolute', - top: '42px', - right: '42px', - cursor: 'pointer', - gap: '16px' - } - } = props; + const { systemConfig } = useSystemConfigStore(); + const userInfo = useSessionStore((state) => state.session); + const { + userMenuStyleProps = { + alignItems: 'center', + position: 'absolute', + top: '42px', + right: '42px', + cursor: 'pointer', + gap: '16px' + } + } = props; - const baseItemStyle = { - w: '36px', - h: '36px', - background: 'rgba(244, 246, 248, 0.7)', - boxShadow: '0px 1.2px 2.3px rgba(0, 0, 0, 0.2)' - }; + const baseItemStyle = { + w: '36px', + h: '36px', + background: 'rgba(244, 246, 248, 0.7)', + boxShadow: '0px 1.2px 2.3px rgba(0, 0, 0, 0.2)' + }; - const buttonList: { - click?: () => void; - button: ReactElement; - content: ReactElement; - key: UserMenuKeys; - }[] = [ - { - key: UserMenuKeys.Notification, - button: ( - - ), - click: () => showDisclosure.onOpen(), - content: ( - setNotificationAmount(amount)} - /> - ) - }, - { - key: UserMenuKeys.Account, - button: ( - user avator - ), - click: () => accountDisclosure.onOpen(), - content: - } - ]; - return ( - - - - {systemConfig?.showGithubStar && } - {buttonList.map((item) => ( - -
- {item.button} -
- {item.content} - {item.key === UserMenuKeys.Notification && notificationAmount > 0 && ( - - )} -
- ))} + const buttonList: { + click?: () => void; + button: ReactElement; + content: ReactElement; + key: UserMenuKeys; + }[] = [ + { + key: UserMenuKeys.Notification, + button: ( + + ), + click: () => showDisclosure.onOpen(), + content: ( + setNotificationAmount(amount)} + /> + ) + }, + { + key: UserMenuKeys.Account, + button: ( + user avator + ), + click: () => accountDisclosure.onOpen(), + content: + } + ]; + return ( + + + + {systemConfig?.showGithubStar && } + {buttonList.map((item) => ( + +
+ {item.button} +
+ {item.content} + {item.key === UserMenuKeys.Notification && notificationAmount > 0 && ( + + )}
- ); + ))} +
+ ); } diff --git a/frontend/desktop/src/pages/api/account/getAccount.ts b/frontend/desktop/src/pages/api/account/getAccount.ts index aca7ab494b0..7db135f8258 100644 --- a/frontend/desktop/src/pages/api/account/getAccount.ts +++ b/frontend/desktop/src/pages/api/account/getAccount.ts @@ -2,7 +2,7 @@ import { GetCRD, K8sApi } from '@/services/backend/kubernetes/user'; import { jsonRes } from '@/services/backend/response'; import { CRDMeta } from '@/types'; import type { NextApiRequest, NextApiResponse } from 'next'; -import { getUserKubeconfig, K8sApiDefault } from '@/services/backend/kubernetes/admin'; +import { getUserKubeconfigNotPatch, K8sApiDefault } from '@/services/backend/kubernetes/admin'; import { verifyAccessToken } from '@/services/backend/auth'; export const AccountMeta: CRDMeta = { group: 'account.sealos.io', @@ -15,7 +15,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) try { const payload = await verifyAccessToken(req.headers); if (!payload) return jsonRes(res, { code: 401, message: 'token is invaild' }); - const kc = await getUserKubeconfig(payload.userCrUid, payload.userCrName); + const kc = await getUserKubeconfigNotPatch(payload.userCrName); if (!kc) return jsonRes(res, { code: 404, message: ' kubeconfig is not found' }); const result = await GetCRD(K8sApiDefault(), AccountMeta, payload.userCrName); jsonRes(res, { data: result?.body }); diff --git a/frontend/desktop/src/pages/api/account/getAmount.ts b/frontend/desktop/src/pages/api/account/getAmount.ts index ca8cf6acf26..e23d4abd121 100644 --- a/frontend/desktop/src/pages/api/account/getAmount.ts +++ b/frontend/desktop/src/pages/api/account/getAmount.ts @@ -1,8 +1,9 @@ import { jsonRes } from '@/services/backend/response'; import type { NextApiRequest, NextApiResponse } from 'next'; -import { getUserKubeconfig } from '@/services/backend/kubernetes/admin'; import process from 'process'; import { verifyAccessToken } from '@/services/backend/auth'; +import { getUserKubeconfigNotPatch } from '@/services/backend/kubernetes/admin'; +import { globalPrisma } from '@/services/backend/db/init'; type accountStatus = { balance: number; @@ -14,35 +15,16 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) if (!base) throw Error("can't ot get alapha1"); const payload = await verifyAccessToken(req.headers); if (!payload) return jsonRes(res, { code: 401, message: 'token is invaild' }); - // get user account payment amount - const kc = await getUserKubeconfig(payload.userCrUid, payload.userCrName); - const body = JSON.stringify({ - kubeConfig: kc, - owner: payload.userCrName - }); - const response = await fetch(base + '/account/v1alpha1/account', { - method: 'POST', - body, - headers: { - 'Content-Type': 'application/json' + const status = await globalPrisma.account.findUnique({ + where: { + userUid: payload.userUid } }); - const data = (await response.clone().json()) as { - account?: { - UserUID: string; - ActivityBonus: number; - EncryptBalance: string; - EncryptDeductionBalance: string; - CreatedAt: Date; - Balance: number; - DeductionBalance: number; - }; - }; - if (!kc || !data?.account) return jsonRes(res, { code: 404, message: 'user is not found' }); + if (!status) return jsonRes(res, { code: 404, message: 'user is not found' }); return jsonRes<{ balance: number; deductionBalance: number }>(res, { data: { - balance: data.account.Balance, - deductionBalance: data.account.DeductionBalance + balance: Number(status.balance || 0), + deductionBalance: Number(status.deduction_balance || 0) } }); } catch (error) { diff --git a/frontend/desktop/src/pages/api/auth/getKubeconfig.ts b/frontend/desktop/src/pages/api/auth/getKubeconfig.ts index c37229fbdfe..f375eee2bfd 100644 --- a/frontend/desktop/src/pages/api/auth/getKubeconfig.ts +++ b/frontend/desktop/src/pages/api/auth/getKubeconfig.ts @@ -1,7 +1,8 @@ import { NextApiRequest, NextApiResponse } from 'next'; import { jsonRes } from '@/services/backend/response'; -import { getUserKubeconfig } from '@/services/backend/kubernetes/admin'; +import { getUserKubeconfigNotPatch } from '@/services/backend/kubernetes/admin'; import { verifyAccessToken } from '@/services/backend/auth'; +import { switchKubeconfigNamespace } from '@/services/backend/kubernetes/user'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { @@ -11,9 +12,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) code: 401, message: 'invalid token' }); - const kubeconfig = await getUserKubeconfig(regionUser.userCrUid, regionUser.userCrName); - if (!kubeconfig) - throw new Error('get kubeconfig error, regionUserUid: ' + regionUser.userCrUid); + const defaultKc = await getUserKubeconfigNotPatch(regionUser.userCrName); + if (!defaultKc) throw new Error('get kubeconfig error, regionUserUid: ' + regionUser.userCrUid); + const kubeconfig = switchKubeconfigNamespace(defaultKc, regionUser.workspaceId); + return jsonRes(res, { code: 200, message: 'Successfully', @@ -24,7 +26,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) } catch (err) { console.log(err); return jsonRes(res, { - message: 'Failed to authenticate with password', + message: 'Failed to get kubeconfig', code: 500 }); } diff --git a/frontend/desktop/src/pages/api/auth/phone/verify.ts b/frontend/desktop/src/pages/api/auth/phone/verify.ts index 457a574b057..9ccdbcc6bff 100644 --- a/frontend/desktop/src/pages/api/auth/phone/verify.ts +++ b/frontend/desktop/src/pages/api/auth/phone/verify.ts @@ -21,7 +21,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) provider: ProviderType.PHONE, id: phoneNumbers, name: phoneNumbers, - avatar_url: '', + avatar_url: '' }); return jsonRes(res, { data, diff --git a/frontend/desktop/src/pages/api/auth/publicWechat/getWechatResult.ts b/frontend/desktop/src/pages/api/auth/publicWechat/getWechatResult.ts index 205dadb34ea..d4722b0a22e 100644 --- a/frontend/desktop/src/pages/api/auth/publicWechat/getWechatResult.ts +++ b/frontend/desktop/src/pages/api/auth/publicWechat/getWechatResult.ts @@ -3,9 +3,9 @@ import { jsonRes } from '@/services/backend/response'; import { TWechatUser } from '@/types/user'; import { getBase64FromRemote } from '@/utils/tools'; import { NextApiRequest, NextApiResponse } from 'next'; -import {getGlobalToken} from "@/services/backend/globalAuth"; -import {ProviderType} from "prisma/global/generated/client"; -import {getRegionToken} from "@/services/backend/regionAuth"; +import { getGlobalToken } from '@/services/backend/globalAuth'; +import { ProviderType } from 'prisma/global/generated/client'; +import { getRegionToken } from '@/services/backend/regionAuth'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { @@ -52,7 +52,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) const data = await getRegionToken({ userUid: _data.user.userUid, userId: _data.user.name - }) + }); return jsonRes(res, { code: 200, message: 'Successfully', diff --git a/frontend/desktop/src/pages/api/auth/regionList.ts b/frontend/desktop/src/pages/api/auth/regionList.ts index 843f292073e..008d03efb1b 100644 --- a/frontend/desktop/src/pages/api/auth/regionList.ts +++ b/frontend/desktop/src/pages/api/auth/regionList.ts @@ -26,7 +26,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) } catch (err) { console.log(err); return jsonRes(res, { - message: 'Failed to authenticate with password', + message: 'Failed to get region list', code: 500 }); } diff --git a/frontend/desktop/src/pages/api/desktop/getInstalledApps.ts b/frontend/desktop/src/pages/api/desktop/getInstalledApps.ts index d758fa7f7c0..863f1e79525 100644 --- a/frontend/desktop/src/pages/api/desktop/getInstalledApps.ts +++ b/frontend/desktop/src/pages/api/desktop/getInstalledApps.ts @@ -2,14 +2,14 @@ import type { NextApiRequest, NextApiResponse } from 'next'; import { K8sApi, ListCRD, switchKubeconfigNamespace } from '@/services/backend/kubernetes/user'; import { jsonRes } from '@/services/backend/response'; import { CRDMeta, TAppCRList, TAppConfig } from '@/types'; -import { getUserKubeconfig } from '@/services/backend/kubernetes/admin'; +import { getUserKubeconfigNotPatch } from '@/services/backend/kubernetes/admin'; import { verifyAccessToken } from '@/services/backend/auth'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { const payload = await verifyAccessToken(req.headers); if (!payload) return jsonRes(res, { code: 401, message: 'token is invaild' }); - const _kc = await getUserKubeconfig(payload.userCrUid, payload.userCrName); + const _kc = await getUserKubeconfigNotPatch(payload.userCrName); if (!_kc) return jsonRes(res, { code: 404, message: 'user is not found' }); const realKc = switchKubeconfigNamespace(_kc, payload.workspaceId); const kc = K8sApi(realKc); diff --git a/frontend/desktop/src/pages/api/dev/removeDuplicates.ts b/frontend/desktop/src/pages/api/dev/removeDuplicates.ts index fb12e65a958..4b4c9c09c2c 100644 --- a/frontend/desktop/src/pages/api/dev/removeDuplicates.ts +++ b/frontend/desktop/src/pages/api/dev/removeDuplicates.ts @@ -18,7 +18,7 @@ async function pullUserData() { .listNamespacedCustomObject('account.sealos.io', 'v1', 'sealos-system', 'accounts'); console.log('get account success'); const accountsMap = new Map( - // @ts-ignore + // @ts-ignore accountsCRList.body.items.map((item) => [ item.metadata.name, [item.status.balance, item.status.deductionBalance] diff --git a/frontend/desktop/src/pages/api/notification/global.ts b/frontend/desktop/src/pages/api/notification/global.ts index 60ae598d242..c3f911ef336 100644 --- a/frontend/desktop/src/pages/api/notification/global.ts +++ b/frontend/desktop/src/pages/api/notification/global.ts @@ -1,4 +1,4 @@ -import { verifyAccessToken} from '@/services/backend/auth'; +import { verifyAccessToken } from '@/services/backend/auth'; import { K8sApiDefault } from '@/services/backend/kubernetes/admin'; import { CRDMeta, ListCRD } from '@/services/backend/kubernetes/user'; import { jsonRes } from '@/services/backend/response'; @@ -10,7 +10,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) const payload = await verifyAccessToken(req.headers); if (!payload) return jsonRes(res, { code: 401, message: 'failed to get info' }); const defaultKc = K8sApiDefault(); - const notification_meta: CRDMeta = { group: 'notification.sealos.io', version: 'v1', diff --git a/frontend/desktop/src/pages/api/notification/list.ts b/frontend/desktop/src/pages/api/notification/list.ts index b9aae06fdb7..81810f7cc97 100644 --- a/frontend/desktop/src/pages/api/notification/list.ts +++ b/frontend/desktop/src/pages/api/notification/list.ts @@ -1,23 +1,31 @@ -import { CRDMeta, K8sApi, ListCRD } from '@/services/backend/kubernetes/user'; +import { + CRDMeta, + K8sApi, + ListCRD, + switchKubeconfigNamespace +} from '@/services/backend/kubernetes/user'; import { jsonRes } from '@/services/backend/response'; import type { NextApiRequest, NextApiResponse } from 'next'; -import { getUserKubeconfig } from '@/services/backend/kubernetes/admin'; import { verifyAccessToken } from '@/services/backend/auth'; +import { getUserKubeconfigNotPatch } from '@/services/backend/kubernetes/admin'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { const payload = await verifyAccessToken(req.headers); if (!payload) return jsonRes(res, { code: 401, message: 'failed to get info' }); - const nsid = payload.workspaceId; - const kc = await getUserKubeconfig(payload.userCrUid, payload.userCrName); + const namespace = payload.workspaceId; + const _kc = await getUserKubeconfigNotPatch(payload.userCrName); + if (!_kc) return jsonRes(res, { code: 404, message: 'user is not found' }); + const realKc = switchKubeconfigNamespace(_kc, namespace); + const kc = K8sApi(realKc); if (!kc) return jsonRes(res, { code: 404, message: 'The kubeconfig is not found' }); const notification_meta: CRDMeta = { group: 'notification.sealos.io', version: 'v1', - namespace: nsid, + namespace, plural: 'notifications' }; - const listCrd = await ListCRD(K8sApi(kc), notification_meta); + const listCrd = await ListCRD(kc, notification_meta); jsonRes(res, { data: listCrd.body }); } catch (err) { console.log(err); diff --git a/frontend/desktop/src/pages/api/notification/read.ts b/frontend/desktop/src/pages/api/notification/read.ts index 54e11fbc9bd..7a747e384f8 100644 --- a/frontend/desktop/src/pages/api/notification/read.ts +++ b/frontend/desktop/src/pages/api/notification/read.ts @@ -1,20 +1,28 @@ import { verifyAccessToken } from '@/services/backend/auth'; -import { CRDMeta, K8sApi, UpdateCRD } from '@/services/backend/kubernetes/user'; +import { + CRDMeta, + K8sApi, + switchKubeconfigNamespace, + UpdateCRD +} from '@/services/backend/kubernetes/user'; import { jsonRes } from '@/services/backend/response'; import type { NextApiRequest, NextApiResponse } from 'next'; -import { getUserKubeconfig } from '@/services/backend/kubernetes/admin'; import * as k8s from '@kubernetes/client-node'; +import { getUserKubeconfigNotPatch } from '@/services/backend/kubernetes/admin'; export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { const { name } = req.body; const payload = await verifyAccessToken(req.headers); if (!payload) return jsonRes(res, { code: 401, message: 'failed to get info' }); - const kc = await getUserKubeconfig(payload.userCrUid, payload.userCrName); - if (!kc) return jsonRes(res, { code: 404, message: 'The kubeconfig is not found' }); + const namespace = payload.workspaceId; + const _kc = await getUserKubeconfigNotPatch(payload.userCrName); + if (!_kc) return jsonRes(res, { code: 404, message: 'user is not found' }); + const realKc = switchKubeconfigNamespace(_kc, namespace); + const kc = K8sApi(realKc); const meta: CRDMeta = { group: 'notification.sealos.io', version: 'v1', - namespace: payload.workspaceId, + namespace, plural: 'notifications' }; @@ -32,7 +40,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) let result = []; for (const n of name) { - let temp = await UpdateCRD(K8sApi(kc), meta, n, patch); + let temp = await UpdateCRD(kc, meta, n, patch); result.push(temp?.body); } jsonRes(res, { data: result }); diff --git a/frontend/desktop/src/services/backend/globalAuth.ts b/frontend/desktop/src/services/backend/globalAuth.ts index 1a25836f233..6fd4384ac1f 100644 --- a/frontend/desktop/src/services/backend/globalAuth.ts +++ b/frontend/desktop/src/services/backend/globalAuth.ts @@ -23,7 +23,15 @@ async function signIn({ provider, id }: { provider: ProviderType; id: string }) }; } -export const hasIniterId = ({inviteeId, inviterId, signResult}: {inviteeId: string, inviterId: string, signResult: any})=>{ +export const hasIniterId = ({ + inviteeId, + inviterId, + signResult +}: { + inviteeId: string; + inviterId: string; + signResult: any; +}) => { const payload = { inviterId, inviteeId, @@ -41,15 +49,14 @@ export const hasIniterId = ({inviteeId, inviterId, signResult}: {inviteeId: stri }, body: JSON.stringify(payload) }) - .then((response) => response.json()) - .then((data) => { - console.log('Upload laf success:', data); - }) - .catch((error) => { - console.error('Upload laf error:', error); - }); - -} + .then((response) => response.json()) + .then((data) => { + console.log('Upload laf success:', data); + }) + .catch((error) => { + console.error('Upload laf error:', error); + }); +}; export async function signInByPassword({ id, password }: { id: string; password: string }) { const userProvider = await globalPrisma.oauthProvider.findUnique({ where: { diff --git a/frontend/desktop/src/services/backend/kubernetes/admin.ts b/frontend/desktop/src/services/backend/kubernetes/admin.ts index 14bbd038126..a76b5e63034 100644 --- a/frontend/desktop/src/services/backend/kubernetes/admin.ts +++ b/frontend/desktop/src/services/backend/kubernetes/admin.ts @@ -1,7 +1,7 @@ import * as k8s from '@kubernetes/client-node'; -// import { customAlphabet } from 'nanoid'; import { k8sFormatTime } from '@/utils/format'; import { StatusCR, UserCR } from '@/types'; +import { KubeConfig } from '@kubernetes/client-node'; export function K8sApiDefault(): k8s.KubeConfig { const kc = new k8s.KubeConfig(); @@ -54,6 +54,7 @@ async function watchClusterObject({ } } } + async function watchCustomClusterObject({ kc, group, @@ -96,6 +97,7 @@ async function watchCustomClusterObject({ } return null; } + async function setUserKubeconfig(kc: k8s.KubeConfig, uid: string, k8s_username: string) { const resourceKind = 'User'; const group = 'user.sealos.io'; @@ -182,6 +184,7 @@ async function setUserTeamCreate(kc: k8s.KubeConfig, k8s_username: string, owner await client.createClusterCustomObject(group, version, plural, resourceObj); return k8s_username; } + async function removeUserTeam(kc: k8s.KubeConfig, k8s_username: string) { const group = 'user.sealos.io'; const version = 'v1'; @@ -209,40 +212,29 @@ async function removeUserTeam(kc: k8s.KubeConfig, k8s_username: string) { ); return k8s_username; } -// 系统迁移 -async function setUserKubeconfigByuid(kc: k8s.KubeConfig, uid: string) { - const resourceType = 'User'; + +export const getUserCr = async (kc: KubeConfig, name: string) => { + const resourceKind = 'User'; const group = 'user.sealos.io'; const version = 'v1'; const plural = 'users'; - const labelSelector = `uid=${uid}`; - let name: string = ''; const client = kc.makeApiClient(k8s.CustomObjectsApi); - const { response } = (await client.listClusterCustomObject( - group, - version, - plural, - undefined, - undefined, - undefined, - undefined, - labelSelector - )) as unknown as { response: { body: { items: any[] } } }; - if (response.body.items.length === 0) { - console.log(`Created new ${resourceType} with labels ${JSON.stringify(labelSelector)}`); - } else { - // 找name - const existingResource = response.body.items[response.body.items.length - 1]; - name = existingResource.metadata.name; - } - return name; -} - -export const getUserKubeconfigByuid = async (uid: string) => { + return await client + .getClusterCustomObject(group, version, plural, name) + .then((res) => res.body as UserCR); +}; +// for enter user state +export const getUserKubeconfigNotPatch = async (name: string) => { const kc = K8sApiDefault(); - return await setUserKubeconfigByuid(kc, uid); + try { + const userCr = await getUserCr(kc, name); + if (userCr?.status?.kubeConfig) return userCr.status.kubeConfig; + else return null; + } catch (e) { + return null; + } }; - +// for update sign in export const getUserKubeconfig = async (uid: string, k8s_username: string) => { const kc = K8sApiDefault(); const group = 'user.sealos.io'; @@ -259,7 +251,7 @@ export const getUserKubeconfig = async (uid: string, k8s_username: string) => { }); return kubeconfig; }; -// 创建Team用的伪user +// for create workspace export const getTeamKubeconfig = async (k8s_username: string, owner: string) => { const kc = K8sApiDefault(); const group = 'user.sealos.io'; diff --git a/frontend/desktop/src/types/crd.ts b/frontend/desktop/src/types/crd.ts index 74983f390ff..d8ea59fecca 100644 --- a/frontend/desktop/src/types/crd.ts +++ b/frontend/desktop/src/types/crd.ts @@ -42,7 +42,7 @@ export type UserCR = { status: string; type: string; }[]; - kubeConfig: KubeConfig; + kubeConfig: string; observedCSRExpirationSeconds: number; observedGeneration: number; phase: string; diff --git a/frontend/providers/applaunchpad/next.config.js b/frontend/providers/applaunchpad/next.config.js index 195e6e04a8b..c0a9ed298d9 100644 --- a/frontend/providers/applaunchpad/next.config.js +++ b/frontend/providers/applaunchpad/next.config.js @@ -1,6 +1,6 @@ /** @type {import('next').NextConfig} */ -const { i18n } = require('./next-i18next.config') -const path = require('path') +const { i18n } = require('./next-i18next.config'); +const path = require('path'); const nextConfig = { i18n, output: 'standalone', @@ -13,14 +13,14 @@ const nextConfig = { issuer: /\.[jt]sx?$/, use: ['@svgr/webpack'] } - ]) - config.plugins = [...config.plugins] - return config + ]); + config.plugins = [...config.plugins]; + return config; }, transpilePackages: ['@sealos/driver'], experimental: { outputFileTracingRoot: path.join(__dirname, '../../') } -} +}; -module.exports = nextConfig +module.exports = nextConfig; diff --git a/frontend/providers/costcenter/src/pages/api/price/bonus.ts b/frontend/providers/costcenter/src/pages/api/price/bonus.ts index 0b4ac163990..d856c183e1c 100644 --- a/frontend/providers/costcenter/src/pages/api/price/bonus.ts +++ b/frontend/providers/costcenter/src/pages/api/price/bonus.ts @@ -54,7 +54,7 @@ export default async function handler(req: NextApiRequest, resp: NextApiResponse .catch(async (err) => { if (retry-- >= 0) { await new Promise((res) => setTimeout(res, 1000)); - await wrdocap(); + await wrap(); } else reject(err); }); wrap(); diff --git a/frontend/providers/cronjob/next.config.js b/frontend/providers/cronjob/next.config.js index 669797e5021..d8e6d56ebd8 100644 --- a/frontend/providers/cronjob/next.config.js +++ b/frontend/providers/cronjob/next.config.js @@ -1,6 +1,6 @@ /** @type {import('next').NextConfig} */ -const { i18n } = require('./next-i18next.config') -const path = require('path') +const { i18n } = require('./next-i18next.config'); +const path = require('path'); const nextConfig = { i18n, output: 'standalone', @@ -13,14 +13,14 @@ const nextConfig = { issuer: /\.[jt]sx?$/, use: ['@svgr/webpack'] } - ]) - config.plugins = [...config.plugins] - return config + ]); + config.plugins = [...config.plugins]; + return config; }, experimental: { // this includes files from the monorepo base two directories up outputFileTracingRoot: path.join(__dirname, '../../') } -} +}; -module.exports = nextConfig +module.exports = nextConfig; diff --git a/frontend/providers/dbprovider/next.config.js b/frontend/providers/dbprovider/next.config.js index 63d097e166b..937468fc318 100644 --- a/frontend/providers/dbprovider/next.config.js +++ b/frontend/providers/dbprovider/next.config.js @@ -1,6 +1,6 @@ /** @type {import('next').NextConfig} */ -const { i18n } = require('./next-i18next.config') -const path = require('path') +const { i18n } = require('./next-i18next.config'); +const path = require('path'); const nextConfig = { i18n, output: 'standalone', @@ -13,15 +13,15 @@ const nextConfig = { issuer: /\.[jt]sx?$/, use: ['@svgr/webpack'] } - ]) - config.plugins = [...config.plugins] - return config + ]); + config.plugins = [...config.plugins]; + return config; }, transpilePackages: ['@sealos/ui', 'sealos-desktop-sdk', '@sealos/driver'], experimental: { // this includes files from the monorepo base two directories up outputFileTracingRoot: path.join(__dirname, '../../') } -} +}; -module.exports = nextConfig +module.exports = nextConfig; diff --git a/frontend/providers/invite/next.config.js b/frontend/providers/invite/next.config.js index 1e922a43025..9408219e1e0 100644 --- a/frontend/providers/invite/next.config.js +++ b/frontend/providers/invite/next.config.js @@ -1,8 +1,8 @@ /** @type {import('next').NextConfig} */ -const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin -const { i18n } = require('./next-i18next.config') -const analyzer = process.env === 'production' ? [new BundleAnalyzerPlugin()] : [] -const path = require('path') +const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; +const { i18n } = require('./next-i18next.config'); +const analyzer = process.env === 'production' ? [new BundleAnalyzerPlugin()] : []; +const path = require('path'); const nextConfig = { i18n, output: 'standalone', @@ -15,15 +15,15 @@ const nextConfig = { issuer: /\.[jt]sx?$/, use: ['@svgr/webpack'] } - ]) - config.plugins = [...config.plugins, ...analyzer] - return config + ]); + config.plugins = [...config.plugins, ...analyzer]; + return config; }, transpilePackages: ['@sealos/ui', 'sealos-desktop-sdk', '@sealos/driver'], experimental: { // this includes files from the monorepo base two directories up outputFileTracingRoot: path.join(__dirname, '../../') } -} +}; -module.exports = nextConfig +module.exports = nextConfig; diff --git a/frontend/providers/license/next.config.js b/frontend/providers/license/next.config.js index 669797e5021..d8e6d56ebd8 100644 --- a/frontend/providers/license/next.config.js +++ b/frontend/providers/license/next.config.js @@ -1,6 +1,6 @@ /** @type {import('next').NextConfig} */ -const { i18n } = require('./next-i18next.config') -const path = require('path') +const { i18n } = require('./next-i18next.config'); +const path = require('path'); const nextConfig = { i18n, output: 'standalone', @@ -13,14 +13,14 @@ const nextConfig = { issuer: /\.[jt]sx?$/, use: ['@svgr/webpack'] } - ]) - config.plugins = [...config.plugins] - return config + ]); + config.plugins = [...config.plugins]; + return config; }, experimental: { // this includes files from the monorepo base two directories up outputFileTracingRoot: path.join(__dirname, '../../') } -} +}; -module.exports = nextConfig +module.exports = nextConfig; diff --git a/frontend/providers/template/next.config.js b/frontend/providers/template/next.config.js index 8eda4d8ecb7..5318b1bdf28 100644 --- a/frontend/providers/template/next.config.js +++ b/frontend/providers/template/next.config.js @@ -1,6 +1,6 @@ /** @type {import('next').NextConfig} */ -const { i18n } = require('./next-i18next.config') -const path = require('path') +const { i18n } = require('./next-i18next.config'); +const path = require('path'); const nextConfig = { i18n, output: 'standalone', @@ -13,15 +13,15 @@ const nextConfig = { issuer: /\.[jt]sx?$/, use: ['@svgr/webpack'] } - ]) - config.plugins = [...config.plugins] - return config + ]); + config.plugins = [...config.plugins]; + return config; }, experimental: { // this includes files from the monorepo base two directories up outputFileTracingRoot: path.join(__dirname, '../../') }, transpilePackages: ['@sealos/ui', 'sealos-desktop-sdk'] -} +}; -module.exports = nextConfig +module.exports = nextConfig;