Skip to content

Commit

Permalink
feat(portal-web): 门户系统去除默认集群选择功能,新增集群选择排序以及记录上次选择集群功能 (#767)
Browse files Browse the repository at this point in the history
## 现状
1. 目前门户系统的集群选择无法决定下拉框展示的顺序,对于大量集群的情况下,用户很难找到自己常用的集群,也无法指定某些集群优先展示。
2. 默认集群功能不完善,无法实现保存用户选择的集群作为下次的默认集群。

## 改动
1. 每个集群配置文件新增配置priority(number类型).
用户给不同的集群配置不同的priority,数字越小,优先级越高,在下拉框以及导航栏中越优先展示。如果没有配置该参数,则默认优先级最低;如果优先级一致则按照displayName的字母排序。
```yaml
# 集群显示名称
displayName: hpc02

adapterUrl: "192.168.88.101:8972"

# 新增
priority: 1

loginNodes:
  # 登录节点展示名称
  - name: login
    # 登录节点的IP或者域名
    # 如果设置的是域名,请确认此节点的/etc/hosts中包含了域名到IP的解析信息
    address: 192.168.88.102
```
2.  去除门户系统右上角的默认集群选择。


![image](https://github.com/PKUHPC/SCOW/assets/130351655/b558b2fe-8dd3-4b95-8930-54d24f8b92b9)

3. 改造默认集群以及集群选择下拉框。将用户上次选择的集群保存在浏览器的LocalStorage中,
key值为``SCOW_DEFAULT_CLUSTER_ID``。如果该值在本地不存在,则取集群优先级最高的集群作为默认集群。


![image](https://github.com/PKUHPC/SCOW/assets/130351655/55b9dc43-bd49-4ab6-8b6d-0a6a34ce8e29)


![image](https://github.com/PKUHPC/SCOW/assets/130351655/5478b975-6bea-4a10-8eca-157926dca71f)

当用户在以下功能中选择集群时,将会修改该值,且作为默认集群在整个门户系统中使用

- 作业模块下的所有集群选择下拉框
- shell 页面的导航栏点击,包括二级路由及三级路由,且点击一级路由会导航至默认集群下的shell页面
- 桌面页面的集群选择下拉框
- 交互式应用页面的导航栏点击,包括二级路由及三级路由,且点击一级路由会导航至默认集群下的交互式应用页面
  • Loading branch information
ZihanChen821 authored Aug 4, 2023
1 parent 4b12bed commit 9f70e21
Show file tree
Hide file tree
Showing 19 changed files with 123 additions and 73 deletions.
5 changes: 5 additions & 0 deletions .changeset/late-clocks-do.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@scow/config": patch
---

集群配置文件新增 priority,提供集群显示排序功能
7 changes: 7 additions & 0 deletions .changeset/unlucky-vans-taste.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@scow/portal-web": patch
"@scow/lib-web": patch
"@scow/docs": patch
---

门户系统去除默认集群选择功能,新增集群选择排序以及记录上次选择集群功能
4 changes: 2 additions & 2 deletions apps/portal-web/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const { readVersionFile } = require("@scow/utils/build/version");
const { getCapabilities } = require("@scow/lib-auth");
const { DEFAULT_PRIMARY_COLOR, getUiConfig } = require("@scow/config/build/ui");
const { getPortalConfig } = require("@scow/config/build/portal");
const { getClusterConfigs, getLoginNode } = require("@scow/config/build/cluster");
const { getClusterConfigs, getLoginNode, getSortedClusters } = require("@scow/config/build/cluster");
const { getCommonConfig } = require("@scow/config/build/common");

/**
Expand Down Expand Up @@ -171,7 +171,7 @@ const buildRuntimeConfig = async (phase, basePath) => {

MIS_URL: config.MIS_DEPLOYED ? (config.MIS_URL || portalConfig.misUrl) : undefined,

CLUSTERS: Object.entries(clusters).map(([id, { displayName }]) => ({ id, name: displayName })),
CLUSTERS: getSortedClusters(clusters),

NOVNC_CLIENT_URL: config.NOVNC_CLIENT_URL,

Expand Down
20 changes: 15 additions & 5 deletions apps/portal-web/src/components/ClusterSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@
*/

import { Select } from "antd";
import dynamic from "next/dynamic";
import { useStore } from "simstate";
import { DefaultClusterStore } from "src/stores/DefaultClusterStore";
import { Cluster, publicConfig } from "src/utils/config";


interface Props {
value?: Cluster[];
onChange?: (clusters: Cluster[]) => void;
Expand All @@ -36,24 +38,32 @@ interface SingleSelectionProps {
value?: Cluster;
onChange?: (cluster: Cluster) => void;
label?: string;
clusters?: Cluster[];
clusterIds?: string[];
}

export const SingleClusterSelector: React.FC<SingleSelectionProps> = ({
value,
onChange,
label,
clusters,
clusterIds,
}) => {

const { setDefaultCluster } = useStore(DefaultClusterStore);

return (
<Select
labelInValue
placeholder="请选择集群"
value={value ? ({ value: value.id, label: value.name }) : undefined}
onChange={({ value, label }) => onChange?.({ id: value, name: label })}
onChange={({ value, label }) => {
onChange?.({ id: value, name: label });
setDefaultCluster({ id: value, name: label });
}
}
options={
(label ? [{ value: label, label, disabled: true }] : [])
.concat((clusters || publicConfig.CLUSTERS).map((x) => ({ value: x.id, label: x.name, disabled: false })))
.concat((publicConfig.CLUSTERS.filter((x) => clusterIds?.includes(x.id) ?? true))
.map((x) => ({ value: x.id, label: x.name, disabled: false })))
}
popupMatchSelectWidth={false}
/>
Expand Down
15 changes: 9 additions & 6 deletions apps/portal-web/src/layouts/BaseLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import { BaseLayout as LibBaseLayout } from "@scow/lib-web/build/layouts/base/Ba
import { JumpToAnotherLink } from "@scow/lib-web/build/layouts/base/header/components";
import { PropsWithChildren, useMemo } from "react";
import { useStore } from "simstate";
import { DefaultClusterSelector } from "src/layouts/DefaultClusterSelector";
import { userRoutes } from "src/layouts/routes";
import { DefaultClusterStore } from "src/stores/DefaultClusterStore";
import { LoginNodeStore } from "src/stores/LoginNodeStore";
Expand All @@ -30,24 +29,28 @@ interface Props {
export const BaseLayout = ({ footerText, versionTag, children }: PropsWithChildren<Props>) => {

const userStore = useStore(UserStore);
const defaultClusterStore = useStore(DefaultClusterStore);
const loginNodes = useStore(LoginNodeStore);
const { defaultCluster, setDefaultCluster, removeDefaultCluster } = useStore(DefaultClusterStore);

const routes = useMemo(() => userRoutes(
userStore.user, defaultClusterStore.cluster, loginNodes,
), [userStore.user, defaultClusterStore.cluster]);
userStore.user, defaultCluster, loginNodes, setDefaultCluster,
), [userStore.user, defaultCluster, setDefaultCluster]);

const logout = () => {
removeDefaultCluster();
userStore.logout();
};

return (
<LibBaseLayout
logout={userStore.logout}
logout={logout}
user={userStore.user}
routes={routes}
footerText={footerText}
versionTag={versionTag}
basePath={publicConfig.BASE_PATH}
headerRightContent={(
<>
<DefaultClusterSelector />
<JumpToAnotherLink
user={userStore.user}
icon={<DatabaseOutlined style={{ paddingRight: 2 }} />}
Expand Down
28 changes: 0 additions & 28 deletions apps/portal-web/src/layouts/DefaultClusterSelector.tsx

This file was deleted.

13 changes: 11 additions & 2 deletions apps/portal-web/src/layouts/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,11 @@ import { join } from "path";
import { User } from "src/stores/UserStore";
import { Cluster, LoginNode, publicConfig } from "src/utils/config";
export const userRoutes: (
user: User | undefined, defaultCluster: Cluster, LoginNodes: Record<string, LoginNode[]>
) => NavItemProps[] = (user, defaultCluster, loginNodes) => {
user: User | undefined,
defaultCluster: Cluster,
LoginNodes: Record<string, LoginNode[]>,
setDefaultCluster: (cluster: Cluster) => void,
) => NavItemProps[] = (user, defaultCluster, loginNodes, setDefaultCluster) => {

if (!user) { return []; }

Expand Down Expand Up @@ -82,11 +85,13 @@ export const userRoutes: (
text: name,
path: `/shell/${id}`,
clickToPath: join(publicConfig.BASE_PATH, "shell", id, loginNodes[id]?.[0]?.name),
handleClick: () => { setDefaultCluster({ name, id }); },
children: loginNodes[id]?.map((loginNode) => ({
openInNewPage: true,
Icon: CloudServerOutlined,
text: loginNode.name,
path: `/shell/${id}/${loginNode.name}`,
handleClick: () => { setDefaultCluster({ name, id }); },
})),
} as NavItemProps)),
} as NavItemProps] : []),
Expand All @@ -106,17 +111,20 @@ export const userRoutes: (
text: cluster.name,
path: `/apps/${cluster.id}`,
clickToPath: `/apps/${cluster.id}/sessions`,
handleClick: () => { setDefaultCluster(cluster); },
children: [
{
Icon: Loading3QuartersOutlined,
text: "已创建的应用",
path: `/apps/${cluster.id}/sessions`,
handleClick: () => { setDefaultCluster(cluster); },
},
{
Icon: PlusOutlined,
text: "创建应用",
clickable: false,
path: `/apps/${cluster.id}/createApps`,
handleClick: () => { setDefaultCluster(cluster); },
},
],
} as NavItemProps)),
Expand All @@ -132,6 +140,7 @@ export const userRoutes: (
text: cluster.name,
path: `/files/${cluster.id}`,
clickToPath: `/files/${cluster.id}/~`,
handleClick: () => { setDefaultCluster(cluster); },
} as NavItemProps)),
}] : []),
...(publicConfig.NAV_LINKS && publicConfig.NAV_LINKS.length > 0
Expand Down
12 changes: 5 additions & 7 deletions apps/portal-web/src/pageComponents/desktop/DesktopTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,18 +47,16 @@ export const DesktopTable: React.FC<Props> = ({ loginDesktopEnabledClusters }) =

const router = useRouter();

const defaultClusterStore = useStore(DefaultClusterStore);

const { defaultCluster } = useStore(DefaultClusterStore);
const loginNodes = useStore(LoginNodeStore);

const clusterQuery = queryToString(router.query.cluster);
const loginQuery = queryToString(router.query.loginNode);

// 如果默认集群没开启登录节点桌面功能,则取开启此功能的某一集群为默认集群。
const defaultCluster = loginDesktopEnabledClusters.includes(defaultClusterStore.cluster)
? defaultClusterStore.cluster
const enabledDefaultCluster = loginDesktopEnabledClusters.find((x) => x.id === defaultCluster.id)
? defaultCluster
: loginDesktopEnabledClusters[0];
const cluster = publicConfig.CLUSTERS.find((x) => x.id === clusterQuery) ?? defaultCluster;
const cluster = publicConfig.CLUSTERS.find((x) => x.id === clusterQuery) ?? enabledDefaultCluster;

const loginNode = loginNodes[cluster.id].find((x) => x.name === loginQuery) ?? undefined;

Expand Down Expand Up @@ -146,7 +144,7 @@ export const DesktopTable: React.FC<Props> = ({ loginDesktopEnabledClusters }) =
onChange={(x) => {
router.push({ query: { cluster: x.id } });
}}
clusters={loginDesktopEnabledClusters}
clusterIds={loginDesktopEnabledClusters.map((x) => x.id)}
/>
</Form.Item>
<Form.Item label="登录节点">
Expand Down
4 changes: 2 additions & 2 deletions apps/portal-web/src/pageComponents/job/AllJobsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,14 @@ export const AllJobQueryTable: React.FC<Props> = ({
userId,
}) => {

const defaultClusterStore = useStore(DefaultClusterStore);
const { defaultCluster } = useStore(DefaultClusterStore);

const [query, setQuery] = useState<FilterForm>(() => {
const now = dayjs();
return {
time: [now.subtract(1, "week").startOf("day"), now.endOf("day")],
jobId: undefined,
cluster: defaultClusterStore.cluster,
cluster: defaultCluster,
};
});

Expand Down
4 changes: 2 additions & 2 deletions apps/portal-web/src/pageComponents/job/JobTemplateTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@ interface ModalProps {

export const JobTemplateTable: React.FC<Props> = () => {

const defaultClusterStore = useStore(DefaultClusterStore);
const { defaultCluster } = useStore(DefaultClusterStore);

const [query, setQuery] = useState<FilterForm>(() => {
return {
cluster: defaultClusterStore.cluster,
cluster: defaultCluster,
};
});

Expand Down
7 changes: 2 additions & 5 deletions apps/portal-web/src/pageComponents/job/RunningJobTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import { runningJobId, RunningJobInfo } from "src/models/job";
import { RunningJobDrawer } from "src/pageComponents/job/RunningJobDrawer";
import { DefaultClusterStore } from "src/stores/DefaultClusterStore";
import { Cluster } from "src/utils/config";

interface FilterForm {
jobId: number | undefined;
cluster: Cluster;
Expand All @@ -34,18 +33,16 @@ interface Props {
}




export const RunningJobQueryTable: React.FC<Props> = ({
userId,
}) => {

const defaultClusterStore = useStore(DefaultClusterStore);
const { defaultCluster } = useStore(DefaultClusterStore);

const [query, setQuery] = useState<FilterForm>(() => {
return {
jobId: undefined,
cluster: defaultClusterStore.cluster,
cluster: defaultCluster,
};
});

Expand Down
4 changes: 2 additions & 2 deletions apps/portal-web/src/pageComponents/job/SubmitJobForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -177,9 +177,9 @@ export const SubmitJobForm: React.FC<Props> = ({ initial = initialValues }) => {
}
}, [currentPartitionInfo]);

const defaultClusterStore = useStore(DefaultClusterStore);
const { defaultCluster: currentDefaultCluster } = useStore(DefaultClusterStore);
// 判断是使用template中的cluster还是系统默认cluster,防止系统配置文件更改时仍选改动前的cluster
const defaultCluster = publicConfig.CLUSTERS.find((x) => x.id === initial.cluster?.id) ?? defaultClusterStore.cluster;
const defaultCluster = publicConfig.CLUSTERS.find((x) => x.id === initial.cluster?.id) ?? currentDefaultCluster;

const memorySize = (currentPartitionInfo ?
currentPartitionInfo.gpus ? nodeCount * gpuCount
Expand Down
5 changes: 2 additions & 3 deletions apps/portal-web/src/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,11 @@ function MyApp({ Component, pageProps, extra }: Props) {
return store;
});

const defaultClusterStore = useConstant(() => {
return createStore(DefaultClusterStore, publicConfig.CLUSTERS[0]);
});

const loginNodeStore = useConstant(() => createStore(LoginNodeStore, extra.loginNodes));

const defaultClusterStore = useConstant(() => createStore(DefaultClusterStore));

// Use the layout defined at the page level, if available
return (
<>
Expand Down
32 changes: 25 additions & 7 deletions apps/portal-web/src/stores/DefaultClusterStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,30 @@
* See the Mulan PSL v2 for more details.
*/

import { useState } from "react";
import { Cluster } from "src/utils/config";
import { useLocalStorage } from "@scow/lib-web/build/utils/hooks";
import { Cluster, publicConfig } from "src/utils/config";

const SCOW_DEFAULT_CLUSTER_ID = "SCOW_DEFAULT_CLUSTER_ID";

export function DefaultClusterStore(defaultCluster: Cluster) {
const [cluster, setCluster] = useState<Cluster>(defaultCluster);

return { cluster, setCluster };
}


export function DefaultClusterStore() {
const [clusterId, setClusterId] = useLocalStorage<String>(
SCOW_DEFAULT_CLUSTER_ID,
publicConfig.CLUSTERS[0].id,
);

const defaultCluster = publicConfig.CLUSTERS.find((cluster) => cluster.id === clusterId) || {} as Cluster;

const setDefaultCluster = (cluster: Cluster) => {
setClusterId(cluster.id);
};

const removeDefaultCluster = () => {
if (typeof window !== "undefined") {
window.localStorage.removeItem(SCOW_DEFAULT_CLUSTER_ID);
}
};

return { defaultCluster, setDefaultCluster, removeDefaultCluster };
}
4 changes: 2 additions & 2 deletions apps/portal-web/src/utils/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,9 @@ export const getLoginDesktopEnabled = (cluster: string): boolean => {

const clusterLoginDesktopEnabled = runtimeConfig.CLUSTERS_CONFIG[cluster]?.loginDesktop?.enabled;

const commonLogindesktopEnabled = runtimeConfig.PORTAL_CONFIG.loginDesktop.enabled;
const commonLoginDesktopEnabled = runtimeConfig.PORTAL_CONFIG.loginDesktop.enabled;

return clusterLoginDesktopEnabled === undefined ? commonLogindesktopEnabled : clusterLoginDesktopEnabled;
return clusterLoginDesktopEnabled === undefined ? commonLoginDesktopEnabled : clusterLoginDesktopEnabled;
};

export type LoginNode = { name: string, address: string }
3 changes: 3 additions & 0 deletions docs/docs/deploy/config/cluster-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ title: 集群配置文件
# 集群显示名称
displayName: hpc01Name

# 集群选择时排序的优先级,数字越小优先级越高,默认优先级最低
priority: 0

# 调度器适配器地址(ip地址:端口号)
adapterUrl: localhost:8972

Expand Down
Loading

0 comments on commit 9f70e21

Please sign in to comment.