Skip to content

Commit

Permalink
feat(web): 优化适配器异常时的页面错误信息展示 (#1182)
Browse files Browse the repository at this point in the history
### 背景
适配器或调度器出现异常导致适配器接口请求无法正常发起的情况,大部分页面仍然报错 `500 undefined`

### 做了什么
此PR主要修改了适配器异常请求无法连接的时候的页面错误处理
在门户系统增加全局错误处理,优化管理系统全局错误处理

**门户系统适配器异常时示例**

![image](https://github.com/PKUHPC/SCOW/assets/43978285/31367fdd-1b29-4fe3-9a3d-e52220a52a67)

![image](https://github.com/PKUHPC/SCOW/assets/43978285/d2b019e3-eb7d-424c-8646-a12658405a74)


![image](https://github.com/PKUHPC/SCOW/assets/43978285/6fcf97d2-eed6-4a99-86c9-546fdc97d085)

对于页面已单独处理过的500请求报错信息保持原有样式

**管理系统适配器异常时示例**
多集群同步操作:

![image](https://github.com/PKUHPC/SCOW/assets/43978285/0b0601a2-77ef-46e2-b2a6-721f876bc851)

![image](https://github.com/PKUHPC/SCOW/assets/43978285/dab08c6a-59e3-45e1-9a5a-35b49f920e0c)

单集群操作:

![image](https://github.com/PKUHPC/SCOW/assets/43978285/ade487ff-8307-4f3d-a1b8-d4fe45dd56db)

![image](https://github.com/PKUHPC/SCOW/assets/43978285/cc33c49d-6fda-4a40-ba82-d105a4186a98)

此PR针对适配器异常情况,优化处理各接口页面报错信息,具体接口及特殊情况备注如下

![image](https://github.com/PKUHPC/SCOW/assets/43978285/2d156c22-f77f-4299-b660-b4687cbf98dd)
  • Loading branch information
piccaSun authored Apr 11, 2024
1 parent eb40caf commit 5c34421
Show file tree
Hide file tree
Showing 38 changed files with 366 additions and 182 deletions.
6 changes: 6 additions & 0 deletions .changeset/tidy-ants-worry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@scow/portal-web": patch
"@scow/mis-web": patch
---

优化集群适配器访问异常时的页面错误信息展示
27 changes: 24 additions & 3 deletions apps/mis-server/src/plugins/clusters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export type ClusterPlugin = {
}

export const CLUSTEROPS_ERROR_CODE = "CLUSTEROPS_ERROR";
export const ADAPTER_CALL_ON_ONE_ERROR = "ADAPTER_CALL_ON_ONE_ERROR";

export const clustersPlugin = plugin(async (f) => {

Expand Down Expand Up @@ -86,7 +87,21 @@ export const clustersPlugin = plugin(async (f) => {
}

logger.info("Calling actions on cluster " + cluster);
return await call(client);

return await call(client).catch((e) => {
logger.error("Cluster ops fails at %o", e);
const reason = "Cluster ID : " + cluster + " Details : " + e;
const clusterErrorDetails = [{
clusterId: cluster,
details: e,
}];
throw new ServiceError({
code: status.INTERNAL,
details: reason,
metadata: scowErrorMetadata(ADAPTER_CALL_ON_ONE_ERROR,
{ clusterErrors: JSON.stringify(clusterErrorDetails) }),
});
});
}),

// throws error if failed.
Expand Down Expand Up @@ -119,11 +134,17 @@ export const clustersPlugin = plugin(async (f) => {

if (failed.length > 0) {
logger.error("Cluster ops fails at clusters %o", failed);
const reason = failed.map((x) => clusters[x.cluster].displayName + ": " + x.error).join("; ");
const reason = failed.map((x) => "Cluster ID : " + x.cluster + " Details : " + x.error).join("; ");

const clusterErrorDetails = failed.map((x) => ({
clusterId: x.cluster,
details: x.error,
}));

throw new ServiceError({
code: status.INTERNAL,
details: reason,
metadata: scowErrorMetadata(CLUSTEROPS_ERROR_CODE),
metadata: scowErrorMetadata(CLUSTEROPS_ERROR_CODE, { clusterErrors: JSON.stringify(clusterErrorDetails) }),
});
}

Expand Down
2 changes: 1 addition & 1 deletion apps/mis-server/src/utils/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ export const scowErrorMetadata = (code: string, extra?: Record<string, MetadataV
SCOW_ERROR_CODE: code,
...extra,
};
};
};
5 changes: 3 additions & 2 deletions apps/mis-web/src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -795,9 +795,10 @@ export default {
extraMessage: "Please visit http://hpc.pku.edu.cn/guide.html for account opening instructions.",
},
_app: {
clusterOpErrorTitle: "Operation Failed",
clusterOpErrorContent: "Multiple cluster operations encountered errors, and some clusters were not "
multiClusterOpErrorTitle: "Operation Failed",
multiClusterOpErrorContent: "Multiple cluster operations encountered errors, and some clusters were not "
+ "synchronized with the modifications.",
adapterConnErrorContent: "The {} cluster is currently unreachable. Please try again later. ",
effectErrorMessage: "Server error occurred!",
},
profile: {
Expand Down
2 changes: 1 addition & 1 deletion apps/mis-web/src/i18n/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const zh_cn = () => import("./zh_cn").then((x) => x.default);
const en = () => import("./en").then((x) => x.default);

// return language type
type LangType = Awaited<ReturnType<typeof zh_cn>>;
export type LangType = Awaited<ReturnType<typeof zh_cn>>;

export const languages = languageDictionary({
zh_cn,
Expand Down
5 changes: 3 additions & 2 deletions apps/mis-web/src/i18n/zh_cn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -795,9 +795,10 @@ export default {
extraMessage: "请访问 http://hpc.pku.edu.cn/guide.html 查看如何开户。",
},
_app: {
clusterOpErrorTitle: "操作失败",
clusterOpErrorContent: "多集群操作出现错误,部分集群未同步修改",
multiClusterOpErrorTitle: "操作失败",
multiClusterOpErrorContent: "多集群操作出现错误,部分集群未同步修改",

adapterConnErrorContent: "{} 集群无法连接,请稍后重试 ",
effectErrorMessage: "服务器出错啦!",
},
profile: {
Expand Down
4 changes: 3 additions & 1 deletion apps/mis-web/src/pageComponents/users/UserTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,9 @@ export const UserTable: React.FC<Props> = ({
.httpError(400, (e) => {
message.destroy("removeUser");
message.error({
content: e.message,
content: `${t("page._app.multiClusterOpErrorContent")}(${
e.message
})`,
duration: 4,
});
})
Expand Down
27 changes: 20 additions & 7 deletions apps/mis-web/src/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { GlobalStyle } from "@scow/lib-web/build/layouts/globalStyle";
import { getHostname } from "@scow/lib-web/build/utils/getHostname";
import { useConstant } from "@scow/lib-web/build/utils/hooks";
import { isServer } from "@scow/lib-web/build/utils/isServer";
import { getCurrentLanguageId } from "@scow/lib-web/build/utils/systemLanguage";
import { getCurrentLanguageId, getI18nConfigCurrentText } from "@scow/lib-web/build/utils/systemLanguage";
// import { getInitialLanguage, getLanguageCookie } from "@scow/lib-web/build/utils/systemLanguage";
import { App as AntdApp } from "antd";
import type { AppContext, AppProps } from "next/app";
Expand All @@ -33,7 +33,7 @@ import { createStore, StoreProvider, useStore } from "simstate";
import { api } from "src/apis";
import { USE_MOCK } from "src/apis/useMock";
import { getTokenFromCookie } from "src/auth/cookie";
import { Provider, useI18nTranslateToString } from "src/i18n";
import { Provider, useI18n, useI18nTranslate } from "src/i18n";
import en from "src/i18n/en";
import zh_cn from "src/i18n/zh_cn";
import { AntdConfigProvider } from "src/layouts/AntdConfigProvider";
Expand All @@ -50,11 +50,13 @@ const languagesMap = {
"en": en,
};


const FailEventHandler: React.FC = () => {
const { message, modal } = AntdApp.useApp();
const userStore = useStore(UserStore);

const t = useI18nTranslateToString();
const languageId = useI18n().currentLanguage.id;
const tArgs = useI18nTranslate();

// 登出过程需要调用的几个方法(logout, useState等)都是immutable的
// 所以不需要每次userStore变化时来重新注册handler
Expand All @@ -64,18 +66,29 @@ const FailEventHandler: React.FC = () => {
userStore.logout();
return;
}
console.log(e);
if (e.data?.code === "CLUSTEROPS_ERROR") {
modal.error({
title: t("page._app.clusterOpErrorTitle"),
content: `${t("page._app.clusterOpErrorContent")}(${
title: tArgs("page._app.multiClusterOpErrorTitle"),
content: `${tArgs("page._app.multiClusterOpErrorContent")}(${
e.data.details
})`,
});
return;
}
if (e.data?.code === "ADAPTER_CALL_ON_ONE_ERROR") {
const clusterId = e.data.clusterErrorsArray[0].clusterId;
const clusterName = clusterId ?
(publicConfig.CLUSTERS[clusterId]?.name ?? clusterId) : undefined;

message.error(`${tArgs("page._app.adapterConnErrorContent",
[getI18nConfigCurrentText(clusterName, languageId)])}(${
e.data.details
})`);
return;
}


message.error(`${t("page._app.effectErrorMessage")}(${e.status}, ${e.data?.code}))`);
message.error(`${tArgs("page._app.effectErrorMessage")}(${e.status}, ${e.data?.code}))`);

});
}, []);
Expand Down
5 changes: 3 additions & 2 deletions apps/mis-web/src/pages/api/admin/fetchJobs/fetchJobs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@
* See the Mulan PSL v2 for more details.
*/

import { typeboxRoute, typeboxRouteSchema } from "@ddadaal/next-typed-api-routes-runtime";
import { typeboxRouteSchema } from "@ddadaal/next-typed-api-routes-runtime";
import { asyncClientCall } from "@ddadaal/tsgrpc-client";
import { AdminServiceClient } from "@scow/protos/build/server/admin";
import { Type } from "@sinclair/typebox";
import { authenticate } from "src/auth/server";
import { PlatformRole } from "src/models/User";
import { getClient } from "src/utils/client";
import { route } from "src/utils/route";

export const FetchJobsSchema = typeboxRouteSchema({
method: "POST",
Expand All @@ -27,7 +28,7 @@ export const FetchJobsSchema = typeboxRouteSchema({
});
const auth = authenticate((info) => info.platformRoles.includes(PlatformRole.PLATFORM_ADMIN));

export default typeboxRoute(FetchJobsSchema,
export default /* #__PURE__*/route(FetchJobsSchema,
async (req, res) => {

const info = await auth(req, res);
Expand Down
5 changes: 3 additions & 2 deletions apps/mis-web/src/pages/api/admin/getClusterUsers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* See the Mulan PSL v2 for more details.
*/

import { typeboxRoute, typeboxRouteSchema } from "@ddadaal/next-typed-api-routes-runtime";
import { typeboxRouteSchema } from "@ddadaal/next-typed-api-routes-runtime";
import { asyncClientCall } from "@ddadaal/tsgrpc-client";
import { AdminServiceClient } from "@scow/protos/build/server/admin";
import { Static, Type } from "@sinclair/typebox";
Expand All @@ -19,6 +19,7 @@ import { PlatformRole } from "src/models/User";
import { ClusterAccountInfo } from "src/models/UserSchemaModel";
import { getClient } from "src/utils/client";
import { queryIfInitialized } from "src/utils/init";
import { route } from "src/utils/route";

// Cannot use GetClusterUsersResponse from protos
export const GetClusterUsersResponse = Type.Object({
Expand All @@ -40,7 +41,7 @@ export const GetClusterUsersSchema = typeboxRouteSchema({

const auth = authenticate((info) => info.platformRoles.includes(PlatformRole.PLATFORM_ADMIN));

export default typeboxRoute(GetClusterUsersSchema,
export default /* #__PURE__*/route(GetClusterUsersSchema,
async (req, res) => {

// if not initialized, every one can import users
Expand Down
5 changes: 3 additions & 2 deletions apps/mis-web/src/pages/api/job/changeJobTimeLimit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* See the Mulan PSL v2 for more details.
*/

import { typeboxRoute, typeboxRouteSchema } from "@ddadaal/next-typed-api-routes-runtime";
import { typeboxRouteSchema } from "@ddadaal/next-typed-api-routes-runtime";
import { asyncClientCall } from "@ddadaal/tsgrpc-client";
import { Status } from "@grpc/grpc-js/build/src/constants";
import { JobServiceClient } from "@scow/protos/build/server/job";
Expand All @@ -21,6 +21,7 @@ import { checkJobAccessible } from "src/server/jobAccessible";
import { callLog } from "src/server/operationLog";
import { getClient } from "src/utils/client";
import { publicConfig } from "src/utils/config";
import { route } from "src/utils/route";
import { handlegRPCError, parseIp } from "src/utils/server";

export type ChangeMode =
Expand Down Expand Up @@ -57,7 +58,7 @@ export const ChangeJobTimeLimitSchema = typeboxRouteSchema({

const auth = authenticate(() => true);

export default typeboxRoute(ChangeJobTimeLimitSchema,
export default /* #__PURE__*/route(ChangeJobTimeLimitSchema,
async (req, res) => {
const info = await auth(req, res);
if (!info) { return; }
Expand Down
5 changes: 3 additions & 2 deletions apps/mis-web/src/pages/api/job/getAvailableBillingTable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* See the Mulan PSL v2 for more details.
*/

import { typeboxRoute, typeboxRouteSchema } from "@ddadaal/next-typed-api-routes-runtime";
import { typeboxRouteSchema } from "@ddadaal/next-typed-api-routes-runtime";
import { asyncClientCall } from "@ddadaal/tsgrpc-client";
import { ConfigServiceClient as MisConfigServerClient } from "@scow/protos/build/server/config";
import { JobBillingItem } from "@scow/protos/build/server/job";
Expand All @@ -20,6 +20,7 @@ import { authenticate } from "src/auth/server";
import { getBillingItems } from "src/pages/api/job/getBillingItems";
import { getClient } from "src/utils/client";
import { moneyToString } from "src/utils/money";
import { route } from "src/utils/route";

import { getUserStatus } from "../dashboard/status";

Expand Down Expand Up @@ -187,7 +188,7 @@ export async function getAvailableBillingTableItems(

}

export default /* #__PURE__*/typeboxRoute(GetAvailableBillingTableSchema, async (req, res) => {
export default /* #__PURE__*/route(GetAvailableBillingTableSchema, async (req, res) => {
const { cluster, tenant, userId } = req.query;
const auth = authenticate(() => true);
const info = await auth(req, res);
Expand Down
9 changes: 8 additions & 1 deletion apps/mis-web/src/pages/api/job/getBillingItems.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,14 @@ export default /* #__PURE__*/typeboxRoute(GetBillingItemsSchema, async (req, res
for (const cluster of publicConfig.CLUSTER_SORTED_ID_LIST) {

const client = getClient(ConfigServiceClient);
const partitions = await asyncClientCall(client, "getClusterConfig", { cluster }).then((resp) => resp.partitions);

const partitions = await asyncClientCall(client, "getClusterConfig", { cluster }).then((resp) => {
return resp.partitions;
}).catch((e) => {
console.log(`Cluster ops fails at ${cluster}, error details: ${e}`);
return [];
});

for (const partition of partitions) {
for (const qos of partition.qos ?? [""]) {
const path = [cluster, partition.name, qos].filter((x) => x).join(".");
Expand Down
5 changes: 3 additions & 2 deletions apps/mis-web/src/pages/api/job/getMissingDefaultPriceItems.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@
* See the Mulan PSL v2 for more details.
*/

import { typeboxRoute, typeboxRouteSchema } from "@ddadaal/next-typed-api-routes-runtime";
import { typeboxRouteSchema } from "@ddadaal/next-typed-api-routes-runtime";
import { asyncClientCall } from "@ddadaal/tsgrpc-client";
import { JobServiceClient } from "@scow/protos/build/server/job";
import { Type } from "@sinclair/typebox";
import { authenticate } from "src/auth/server";
import { PlatformRole } from "src/models/User";
import { getClient } from "src/utils/client";
import { queryIfInitialized } from "src/utils/init";
import { route } from "src/utils/route";

export const GetMissingDefaultPriceItemsSchema = typeboxRouteSchema({
method: "GET",
Expand All @@ -29,7 +30,7 @@ export const GetMissingDefaultPriceItemsSchema = typeboxRouteSchema({

const auth = authenticate((info) => info.platformRoles.includes(PlatformRole.PLATFORM_ADMIN));

export default typeboxRoute(GetMissingDefaultPriceItemsSchema,
export default /* #__PURE__*/route(GetMissingDefaultPriceItemsSchema,
async (req, res) => {

// 如果还未初始化,本接口不做登录校验
Expand Down
5 changes: 3 additions & 2 deletions apps/mis-web/src/pages/api/job/queryJobTimeLimit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* See the Mulan PSL v2 for more details.
*/

import { typeboxRoute, typeboxRouteSchema } from "@ddadaal/next-typed-api-routes-runtime";
import { typeboxRouteSchema } from "@ddadaal/next-typed-api-routes-runtime";
import { asyncClientCall } from "@ddadaal/tsgrpc-client";
import { Status } from "@grpc/grpc-js/build/src/constants";
import { JobServiceClient } from "@scow/protos/build/server/job";
Expand All @@ -19,6 +19,7 @@ import { authenticate } from "src/auth/server";
import { TenantRole } from "src/models/User";
import { checkJobAccessible } from "src/server/jobAccessible";
import { getClient } from "src/utils/client";
import { route } from "src/utils/route";
import { handlegRPCError } from "src/utils/server";

export const QueryJobTimeLimitSchema = typeboxRouteSchema({
Expand All @@ -44,7 +45,7 @@ export const QueryJobTimeLimitSchema = typeboxRouteSchema({

const auth = authenticate((info) => info.tenantRoles.includes(TenantRole.TENANT_ADMIN));

export default typeboxRoute(QueryJobTimeLimitSchema,
export default /* #__PURE__*/route(QueryJobTimeLimitSchema,
async (req, res) => {

const info = await auth(req, res);
Expand Down
5 changes: 3 additions & 2 deletions apps/mis-web/src/pages/api/job/runningJobs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@
* See the Mulan PSL v2 for more details.
*/

import { typeboxRoute, typeboxRouteSchema } from "@ddadaal/next-typed-api-routes-runtime";
import { typeboxRouteSchema } from "@ddadaal/next-typed-api-routes-runtime";
import { asyncClientCall } from "@ddadaal/tsgrpc-client";
import { GetRunningJobsRequest, JobServiceClient } from "@scow/protos/build/server/job";
import { Static, Type } from "@sinclair/typebox";
import { authenticate } from "src/auth/server";
import { TenantRole } from "src/models/User";
import { getClient } from "src/utils/client";
import { route } from "src/utils/route";

// Cannot use RunningJob from protos
export const RunningJob = Type.Object({
Expand Down Expand Up @@ -81,7 +82,7 @@ export const getRunningJobs = async (request: GetRunningJobsRequest) => {
};


export default typeboxRoute(GetRunningJobsSchema, async (req, res) => {
export default /* #__PURE__*/route(GetRunningJobsSchema, async (req, res) => {
const auth = authenticate((u) =>
// u.platformRoles.includes(PlatformRole.PLATFORM_ADMIN) ||
u.tenantRoles.includes(TenantRole.TENANT_ADMIN) ||
Expand Down
Loading

0 comments on commit 5c34421

Please sign in to comment.