From 0275a9e412a8bfe6d14ef7903c4e7b3f712fc58a Mon Sep 17 00:00:00 2001 From: Yixin Sun <43978285+piccaSun@users.noreply.github.com> Date: Mon, 8 Jul 2024 21:24:13 +0800 Subject: [PATCH] =?UTF-8?q?fix(mis):=20=E7=B3=BB=E7=BB=9F=E5=88=9D?= =?UTF-8?q?=E5=A7=8B=E5=8C=96=E6=97=B6=E5=8F=AF=E7=94=A8=E9=9B=86=E7=BE=A4?= =?UTF-8?q?=E6=8A=A5=E9=94=99=20(#1345)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### 问题 因初始化时无法通过token或auth鉴权,导致系统初始化时的可用集群查询结果为空 ![image](https://github.com/PKUHPC/SCOW/assets/43978285/8b5fa308-6044-4622-9e81-15e055d7fa01) ### 修改 在管理系统的web端原有获取集群配置信息接口以外增加返回简单集群信息的web接口 `getSimpleClustersInfoFromConfigFiles` 返回信息仅包含 `clusterId, displayName,` 及用于优先级排序的 `priority` 在`getSimpleClustersInfoFromConfigFiles` 及获取当前集群在线信息接口中` getClustersRuntimeInfo` 增加是否已初始化判断,未初始化时允许所有人获取简单集群在线信息 ### 修改后 系统初始化时获取集群信息正常,各操作正常,初始化完成后登录正常 ![集群停用初始化](https://github.com/PKUHPC/SCOW/assets/43978285/3149453e-f0b0-495b-8c40-8edf391211ed) faa3afca5e2e2cc4219edc077f2ef6f --- .changeset/chilled-schools-pull.md | 8 +++ apps/mis-web/src/apis/api.mock.ts | 10 +++ apps/mis-web/src/apis/api.ts | 6 +- apps/mis-web/src/pages/_app.tsx | 44 +++++++----- .../pages/api/admin/getClustersRuntimeInfo.ts | 14 ++-- .../src/pages/api/clusterConfigsInfo.ts | 5 +- .../src/pages/api/simpleClustersInfo.ts | 68 +++++++++++++++++++ apps/mis-web/src/stores/ClusterInfoStore.ts | 13 ++-- apps/mis-web/src/utils/cluster.ts | 15 ++-- libs/config/src/cluster.ts | 7 ++ libs/ssh/src/ssh.ts | 1 - libs/web/src/utils/cluster.ts | 7 +- 12 files changed, 155 insertions(+), 43 deletions(-) create mode 100644 .changeset/chilled-schools-pull.md create mode 100644 apps/mis-web/src/pages/api/simpleClustersInfo.ts diff --git a/.changeset/chilled-schools-pull.md b/.changeset/chilled-schools-pull.md new file mode 100644 index 0000000000..2e896a185f --- /dev/null +++ b/.changeset/chilled-schools-pull.md @@ -0,0 +1,8 @@ +--- +"@scow/mis-web": patch +"@scow/config": patch +"@scow/lib-ssh": patch +"@scow/lib-web": patch +--- + +修复系统初始化时因无法通过鉴权可用集群为空的问题 diff --git a/apps/mis-web/src/apis/api.mock.ts b/apps/mis-web/src/apis/api.mock.ts index 135ddacafc..137875c6ef 100644 --- a/apps/mis-web/src/apis/api.mock.ts +++ b/apps/mis-web/src/apis/api.mock.ts @@ -503,6 +503,16 @@ export const mockApi: MockApi = { }, } }), + getSimpleClustersInfoFromConfigFiles: async () => ({ + clustersInfo: { + hpc01: { + displayName: "hpc01Name", + priority: 1, + clusterId: "hpc01", + }, + }, + }), + getClustersConnectionInfo: async () => ({ results: [{ clusterId: "hpc01", schedulerName: "hpc", diff --git a/apps/mis-web/src/apis/api.ts b/apps/mis-web/src/apis/api.ts index 083c6dbff6..b0f14d1d98 100644 --- a/apps/mis-web/src/apis/api.ts +++ b/apps/mis-web/src/apis/api.ts @@ -13,7 +13,7 @@ /* eslint-disable max-len */ import { apiClient } from "src/apis/client"; -import type { getClusterConfigFilesSchema } from "src/pages/api//clusterConfigsInfo"; +import type { GetClusterConfigFilesSchema } from "src/pages/api//clusterConfigsInfo"; import type { ActivateClusterSchema } from "src/pages/api/admin/activateCluster"; import type { ChangeJobPriceSchema } from "src/pages/api/admin/changeJobPrice"; import type { ChangePasswordAsPlatformAdminSchema } from "src/pages/api/admin/changePassword"; @@ -93,6 +93,7 @@ import type { GetOperationLogsSchema } from "src/pages/api/log/getOperationLog"; import type { ChangeEmailSchema } from "src/pages/api/profile/changeEmail"; import type { ChangePasswordSchema } from "src/pages/api/profile/changePassword"; import type { CheckPasswordSchema } from "src/pages/api/profile/checkPassword"; +import { GetSimpleClustersInfoFromConfigFilesSchema } from "src/pages/api/simpleClustersInfo"; import type { DewhitelistAccountSchema } from "src/pages/api/tenant/accountWhitelist/dewhitelistAccount"; import type { GetWhitelistedAccountsSchema } from "src/pages/api/tenant/accountWhitelist/getWhitelistedAccounts"; import type { WhitelistAccountSchema } from "src/pages/api/tenant/accountWhitelist/whitelistAccount"; @@ -167,7 +168,7 @@ export const api = { authCallback: apiClient.fromTypeboxRoute("GET", "/api/auth/callback"), logout: apiClient.fromTypeboxRoute("DELETE", "/api/auth/logout"), validateToken: apiClient.fromTypeboxRoute("GET", "/api/auth/validateToken"), - getClusterConfigFiles: apiClient.fromTypeboxRoute("GET", "/api//clusterConfigsInfo"), + getClusterConfigFiles: apiClient.fromTypeboxRoute("GET", "/api//clusterConfigsInfo"), getUserStatus: apiClient.fromTypeboxRoute("GET", "/api/dashboard/status"), exportAccount: apiClient.fromTypeboxRoute("GET", "/api/file/exportAccount"), exportChargeRecord: apiClient.fromTypeboxRoute("GET", "/api/file/exportChargeRecord"), @@ -226,4 +227,5 @@ export const api = { queryStorageUsage: apiClient.fromTypeboxRoute("GET", "/api/users/storageUsage"), unblockUserInAccount: apiClient.fromTypeboxRoute("PUT", "/api/users/unblockInAccount"), unsetAdmin: apiClient.fromTypeboxRoute("PUT", "/api/users/unsetAdmin"), + getSimpleClustersInfoFromConfigFiles: apiClient.fromTypeboxRoute("GET", "/api//simpleClustersInfo"), }; diff --git a/apps/mis-web/src/pages/_app.tsx b/apps/mis-web/src/pages/_app.tsx index b1095fb575..1226687396 100644 --- a/apps/mis-web/src/pages/_app.tsx +++ b/apps/mis-web/src/pages/_app.tsx @@ -14,7 +14,7 @@ import "nprogress/nprogress.css"; import "antd/dist/reset.css"; import { failEvent } from "@ddadaal/next-typed-api-routes-runtime/lib/client"; -import { ClusterConfigSchema } from "@scow/config/build/cluster"; +import { ClusterConfigSchema, SimpleClusterSchema } from "@scow/config/build/cluster"; import { UiExtensionStore } from "@scow/lib-web/build/extensions/UiExtensionStore"; import { DarkModeCookie, DarkModeProvider, getDarkModeCookieValue } from "@scow/lib-web/build/layouts/darkMode"; import { GlobalStyle } from "@scow/lib-web/build/layouts/globalStyle"; @@ -46,6 +46,7 @@ import { } from "src/stores/UserStore"; import { Cluster, getPublicConfigClusters } from "src/utils/cluster"; import { publicConfig, runtimeConfig } from "src/utils/config"; +import { queryIfInitialized } from "src/utils/init"; const languagesMap = { "zh_cn": zh_cn, @@ -137,8 +138,9 @@ interface ExtraProps { footerText: string; darkModeCookieValue: DarkModeCookie | undefined; initialLanguage: string; - clusterConfigs: { [clusterId: string]: ClusterConfigSchema; }; - initialActivatedClusters: {[clusterId: string]: Cluster}; + clusterConfigs: Record; + initialActivatedClusters: Record; + initialSimpleClustersInfo: Record; } type Props = AppProps & { extra: ExtraProps }; @@ -153,7 +155,7 @@ function MyApp({ Component, pageProps, extra }: Props) { }); const clusterInfoStore = useConstant(() => { - return createStore(ClusterInfoStore, extra.clusterConfigs, extra.initialActivatedClusters); + return createStore(ClusterInfoStore, extra.clusterConfigs, extra.initialActivatedClusters, extra.initialSimpleClustersInfo); }); const uiExtensionStore = useConstant(() => createStore(UiExtensionStore, publicConfig.UI_EXTENSION)); @@ -216,6 +218,7 @@ MyApp.getInitialProps = async (appContext: AppContext) => { initialLanguage: "", clusterConfigs: {}, initialActivatedClusters: {}, + initialSimpleClustersInfo: {}, }; // This is called on server on first load, and on client on every page transition @@ -246,24 +249,31 @@ MyApp.getInitialProps = async (appContext: AppContext) => { const clusterConfigs = data?.clusterConfigs; if (clusterConfigs && Object.keys(clusterConfigs).length > 0) { - extra.clusterConfigs = clusterConfigs; - const publicConfigClusters - = getPublicConfigClusters(clusterConfigs); - // get initial activated clusters - const clustersRuntimeInfo = - await api.getClustersRuntimeInfo({ query: { token } }).then((x) => x, () => undefined); - - const activatedClusters - = formatActivatedClusters({ - clustersRuntimeInfo: clustersRuntimeInfo?.results, - misConfigClusters: publicConfigClusters }); - extra.initialActivatedClusters = activatedClusters.misActivatedClusters ?? {}; - } } } + const clustersRuntimeInfo = token ? + await api.getClustersRuntimeInfo({ query: { token } }).then((x) => x, () => undefined) + : await api.getClustersRuntimeInfo({ query: { } }).then((x) => x, () => undefined); + + // get deployed clusters' simple info (only clusterId, displayName and priority) + const simpleClustersInfo + = await api.getSimpleClustersInfoFromConfigFiles({}).then((x) => x, () => ({ clustersInfo: {} })); + + extra.initialSimpleClustersInfo = simpleClustersInfo?.clustersInfo; + + const publicConfigClusters = Object.keys(extra.clusterConfigs).length > 0 ? + getPublicConfigClusters(extra.clusterConfigs) : getPublicConfigClusters(extra.initialSimpleClustersInfo) ?? {}; + + const activatedClusters + = formatActivatedClusters({ + clustersRuntimeInfo: clustersRuntimeInfo?.results, + misConfigClusters: publicConfigClusters }); + + extra.initialActivatedClusters = activatedClusters.misActivatedClusters ?? {}; + const hostname = getHostname(appContext.ctx.req); extra.primaryColor = (hostname && runtimeConfig.UI_CONFIG?.primaryColor?.hostnameMap?.[hostname]) diff --git a/apps/mis-web/src/pages/api/admin/getClustersRuntimeInfo.ts b/apps/mis-web/src/pages/api/admin/getClustersRuntimeInfo.ts index c35d0de6f4..52932e9c81 100644 --- a/apps/mis-web/src/pages/api/admin/getClustersRuntimeInfo.ts +++ b/apps/mis-web/src/pages/api/admin/getClustersRuntimeInfo.ts @@ -20,6 +20,7 @@ import { authenticate } from "src/auth/server"; import { validateToken } from "src/auth/token"; import { getClusterConfigFiles } from "src/server/clusterConfig"; import { getClient } from "src/utils/client"; +import { queryIfInitialized } from "src/utils/init"; import { route } from "src/utils/route"; export const GetClustersRuntimeInfoSchema = typeboxRouteSchema({ @@ -43,11 +44,14 @@ const auth = authenticate(() => true); export default route(GetClustersRuntimeInfoSchema, async (req, res) => { - const { token } = req.query; - // when firstly used in getInitialProps, check the token - // when logged in, use auth() - const info = token ? await validateToken(token) : await auth(req, res); - if (!info) { return; } + // if not initialized, every one can get clustersRuntimeInfo + if (await queryIfInitialized()) { + const { token } = req.query; + // when firstly used in getInitialProps, check the token + // when logged in, use auth() + const info = token ? await validateToken(token) : await auth(req, res); + if (!info) { return; } + } const client = getClient(ConfigServiceClient); const result = await asyncClientCall(client, "getClustersRuntimeInfo", {}); diff --git a/apps/mis-web/src/pages/api/clusterConfigsInfo.ts b/apps/mis-web/src/pages/api/clusterConfigsInfo.ts index 8af71b648a..18ed7c1445 100644 --- a/apps/mis-web/src/pages/api/clusterConfigsInfo.ts +++ b/apps/mis-web/src/pages/api/clusterConfigsInfo.ts @@ -28,10 +28,11 @@ import { Type } from "@sinclair/typebox"; import { authenticate } from "src/auth/server"; import { validateToken } from "src/auth/token"; import { getClusterConfigFiles } from "src/server/clusterConfig"; +import { queryIfInitialized } from "src/utils/init"; import { route } from "src/utils/route"; -export const getClusterConfigFilesSchema = typeboxRouteSchema({ +export const GetClusterConfigFilesSchema = typeboxRouteSchema({ method: "GET", // only set the query value when firstly used in getInitialProps @@ -48,7 +49,7 @@ export const getClusterConfigFilesSchema = typeboxRouteSchema({ const auth = authenticate(() => true); -export default route(getClusterConfigFilesSchema, +export default route(GetClusterConfigFilesSchema, async (req, res) => { const { token } = req.query; diff --git a/apps/mis-web/src/pages/api/simpleClustersInfo.ts b/apps/mis-web/src/pages/api/simpleClustersInfo.ts new file mode 100644 index 0000000000..3b5f153114 --- /dev/null +++ b/apps/mis-web/src/pages/api/simpleClustersInfo.ts @@ -0,0 +1,68 @@ +/** + * Copyright (c) 2022 Peking University and Peking University Institute for Computing and Digital Economy + * SCOW is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +/** + * Copyright (c) 2022 Peking University and Peking University Institute for Computing and Digital Economy + * SCOW is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +import { typeboxRouteSchema } from "@ddadaal/next-typed-api-routes-runtime"; +import { getClusterConfigFiles } from "src/server/clusterConfig"; +import { ClusterConfigSchema, SimpleClusterSchema } from "@scow/config/build/cluster"; +import { route } from "src/utils/route"; +import { Type } from "@sinclair/typebox"; +import { queryIfInitialized } from "src/utils/init"; +import { authenticate } from "src/auth/server"; + +export const GetSimpleClustersInfoFromConfigFilesSchema = typeboxRouteSchema({ + method: "GET", + + responses: { + + 200: Type.Object({ + clustersInfo: Type.Record(Type.String(), SimpleClusterSchema) }), + }, +}); + +const auth = authenticate(() => true); +export default route(GetSimpleClustersInfoFromConfigFilesSchema, + async (req, res) => { + + // if not initialized, every one can getSimpleClusterInfo which includes clusterId, displayedName and priority + if (await queryIfInitialized()) { + const info = await auth(req, res); + if (!info) { return; } + } + + const clustersFullInfo: Record = await getClusterConfigFiles(); + + const clustersInfo: Record = {}; + + Object.keys(clustersFullInfo).forEach(key => { + clustersInfo[key] = { + clusterId: key, + displayName: clustersFullInfo[key].displayName, + priority: clustersFullInfo[key].priority, + }; + }); + + return { + 200: { clustersInfo }, + }; + }); diff --git a/apps/mis-web/src/stores/ClusterInfoStore.ts b/apps/mis-web/src/stores/ClusterInfoStore.ts index 36161fc8fd..b780649d7c 100644 --- a/apps/mis-web/src/stores/ClusterInfoStore.ts +++ b/apps/mis-web/src/stores/ClusterInfoStore.ts @@ -10,23 +10,24 @@ * See the Mulan PSL v2 for more details. */ -import { ClusterConfigSchema } from "@scow/config/build/cluster"; +import { ClusterConfigSchema, SimpleClusterSchema } from "@scow/config/build/cluster"; import { getSortedClusterIds } from "@scow/lib-web/build/utils/cluster"; import { useEffect, useState } from "react"; import { Cluster, getPublicConfigClusters } from "src/utils/cluster"; // export function ClusterInfoStore( export function ClusterInfoStore( - clusterConfigs: {[clusterId: string]: ClusterConfigSchema}, - initialActivatedClusters: {[clusterId: string]: Cluster}, + clusterConfigs: Record, + initialActivatedClusters: Record, + initialSimpleClusters: Record, ) { - const publicConfigClusters = getPublicConfigClusters(clusterConfigs); + const publicConfigClusters = getPublicConfigClusters(clusterConfigs) ?? getPublicConfigClusters(initialSimpleClusters) ?? {}; - const clusterSortedIdList = getSortedClusterIds(clusterConfigs); + const clusterSortedIdList = getSortedClusterIds(clusterConfigs) ?? getSortedClusterIds(initialSimpleClusters) ?? []; const [activatedClusters, setActivatedClusters] - = useState<{[clusterId: string]: Cluster}>(initialActivatedClusters); + = useState>(initialActivatedClusters); const initialDefaultClusterId = clusterSortedIdList.find((x) => { return Object.keys(initialActivatedClusters).find((c) => c === x); diff --git a/apps/mis-web/src/utils/cluster.ts b/apps/mis-web/src/utils/cluster.ts index 1950d810e1..ea40e67054 100644 --- a/apps/mis-web/src/utils/cluster.ts +++ b/apps/mis-web/src/utils/cluster.ts @@ -10,7 +10,7 @@ * See the Mulan PSL v2 for more details. */ -import { ClusterConfigSchema } from "@scow/config/build/cluster"; +import { SimpleClusterSchema } from "@scow/config/build/cluster"; import { I18nStringType } from "@scow/config/build/i18n"; import { getI18nConfigCurrentText } from "@scow/lib-web/build/utils/systemLanguage"; @@ -19,13 +19,13 @@ export type Cluster = { id: string; name: I18nStringType; } export const getClusterName = ( clusterId: string, languageId: string, - publicConfigClusters: { [clusterId: string]: Cluster }, + publicConfigClusters: Record, ) => { return getI18nConfigCurrentText(publicConfigClusters[clusterId]?.name, languageId) || clusterId; }; export const getSortedClusterValues = - (publicConfigClusters: { [clusterId: string]: Cluster }, + (publicConfigClusters: Record, clusterSortedIdList: string[], ): Cluster[] => { @@ -39,18 +39,19 @@ export const getSortedClusterValues = export const getPublicConfigClusters = - (configClusters: Record): - { [clusterId: string]: Cluster } => { + (configClusters: Record>): + Record => { - const publicConfigClusters: { [clusterId: string]: Cluster; } = {}; + const publicConfigClusters: Record = {}; Object.keys(configClusters).forEach((clusterId) => { const cluster = { id: clusterId, - name: configClusters[clusterId].displayName, + name: configClusters[clusterId].displayName!, }; publicConfigClusters[clusterId] = cluster; }); return publicConfigClusters; }; + diff --git a/libs/config/src/cluster.ts b/libs/config/src/cluster.ts index 8b1c36f28c..7a02e646a2 100644 --- a/libs/config/src/cluster.ts +++ b/libs/config/src/cluster.ts @@ -18,6 +18,13 @@ import { Logger } from "ts-log"; const CLUSTER_CONFIG_BASE_PATH = "clusters"; +export const SimpleClusterSchema = Type.Object({ + clusterId: Type.String(), + displayName: createI18nStringSchema({ description: "集群名称" }), + priority: Type.Number(), +}); +export type SimpleClusterSchema = Static; + export enum k8sRuntime { docker = "docker", containerd = "containerd", diff --git a/libs/ssh/src/ssh.ts b/libs/ssh/src/ssh.ts index 8969fbbac9..f6365f3f1a 100644 --- a/libs/ssh/src/ssh.ts +++ b/libs/ssh/src/ssh.ts @@ -210,7 +210,6 @@ export async function executeAsUser( */ export async function testRootUserSshLogin(host: string, keyPair: KeyPair, logger: Logger) { return await sshConnect(host, "root", keyPair, logger, async () => undefined).catch((e) => e); - } /** diff --git a/libs/web/src/utils/cluster.ts b/libs/web/src/utils/cluster.ts index a195c1e15f..40373c0486 100644 --- a/libs/web/src/utils/cluster.ts +++ b/libs/web/src/utils/cluster.ts @@ -10,13 +10,14 @@ * See the Mulan PSL v2 for more details. */ -import { Cluster as ClusterWithConfig, ClusterConfigSchema } from "@scow/config/build/cluster"; +import { Cluster as ClusterWithConfig, ClusterConfigSchema, SimpleClusterSchema } from "@scow/config/build/cluster"; -export const getSortedClusterIds = (clusters: Record): string[] => { +export const getSortedClusterIds = (clusters: Record>): string[] => { return Object.keys(clusters) .sort( (a, b) => { - return clusters[a].priority - clusters[b].priority; + return (clusters[a].priority ?? Number.MAX_SAFE_INTEGER) + - (clusters[b].priority ?? Number.MIN_SAFE_INTEGER); }, ); };