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)
---
.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);
},
);
};