Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion esp/src/.github/instructions/react-coding.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ Apply the [general coding guidelines](./general-coding.instructions.md) to all c
## React Guidelines
- Use functional components with hooks (avoid class components).
- Follow the React hooks rules (no conditional or nested hooks).
- Use `React.FC` type for components with children, and define prop types explicitly.
- Use `React.FunctionComponent` type for components with children, and define prop types explicitly.
- Prefer `useStyles` over inline styles for consistency and performance.
- Keep components small, focused, and reusable; follow single-responsibility principle.
- Use CSS modules or CSS-in-JS for component-level styling; avoid global styles.
Comment thread
GordonSmith marked this conversation as resolved.
- Use `useCallback`, `useMemo`, and `React.memo` for performance optimization.
Expand Down
1 change: 1 addition & 0 deletions esp/src/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ types/
tmp/
.vscode/*
!.vscode/tasks.json
tests/test-wus.json
lws.target.txt
tmp.*
11 changes: 11 additions & 0 deletions esp/src/src-dojo/nls/hpcc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ export = {
ActivePackageMap: "Active Package Map",
ActiveWorkunit: "Active Workunit",
Activities: "Activities",
ActivitiesLegacy: "Activities (L)",
ActivitiesPreview: "Activities (P)",
Activity: "Activity",
ActivityLabel: "Activity Label",
ActivityMap: "Activity Map",
Expand Down Expand Up @@ -379,6 +381,7 @@ export = {
FirstNRows: "First N Rows",
Fixed: "Fixed",
Folder: "Folder",
Folders: "Folders",
Form: "Form",
Format: "Format",
Forums: "Forums",
Expand Down Expand Up @@ -422,6 +425,7 @@ export = {
HideSpills: "Hide Spills",
High: "High",
History: "History",
Home: "Home",
Homepage: "Homepage",
Hotspots: "Hot spots",
Hour: "Hour",
Expand Down Expand Up @@ -571,6 +575,7 @@ export = {
MatchCase: "Match Case",
MatchWholeWord: "Match Whole Word",
Max: "Max",
Maximize: "Maximize",
MaxConnections: "Max Connections",
MaxNode: "Max Node",
MaxSize: "Max Size",
Expand All @@ -592,6 +597,7 @@ export = {
MetricsGraph: "Metrics/Graph",
MetricsSQL: "Metrics (SQL)",
Min: "Min",
Minimize: "Minimize",
MinimumCompileCost: "Minimum Compile Cost",
MinimumExecuteCost: "Minimum Execute Cost",
MinimumFileAccessCost: "Minimum File Access Cost",
Expand All @@ -617,6 +623,11 @@ export = {
Month: "Month",
More: "more",
Move: "Move",
MoveBottomHint: "Move Bottom (ctrl+click-down)",
MoveDown: "Move Down",
MoveDownHint: "Move Down (ctrl+click move to bottom)",
MoveUp: "Move Up",
MoveUpHint: "Move Up (ctrl+click move to top)",
MustContainUppercaseAndSymbol: "Must contain uppercase and symbol",
NA: "N/A",
Name: "Name",
Expand Down
2 changes: 1 addition & 1 deletion esp/src/src-dojo/nls/zh/hpcc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@
AutoRefreshIncrement: "自动刷新增量",
Back: "返回",
BackupDFUWorkunit: "备份DFU工作单元",
BackupECLWorkunit: "备份ECL工作单元",
BackupECLWorkunit: "备份ECL工作单元",
BannerColor: "标语的颜色",
BannerColorTooltip: "修改上方显示框的背景颜色",
BannerMessage: "标语的文字",
Expand Down
49 changes: 49 additions & 0 deletions esp/src/src-react/components/ActivitiesCards.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import * as React from "react";
import { Toolbar, ToolbarButton, Divider } from "@fluentui/react-components";
import { ArrowClockwise20Regular } from "@fluentui/react-icons";
import nlsHPCC from "src/nlsHPCC";
import { QueueCards } from "./cards/QueueCard";
import { HolyGrail } from "../layouts/HolyGrail";
import { SizeMe } from "../layouts/SizeMe";
import { useBuildInfo } from "../hooks/platform";
import { DiskUsageCards } from "./cards/DiskUsageCard";

interface ActivitiesProps {
}

export const Activities: React.FunctionComponent<ActivitiesProps> = ({
}) => {
const [, { isContainer }] = useBuildInfo();
const [refreshToken, setRefreshToken] = React.useState(0);

return <HolyGrail
header={
<Toolbar>
<ToolbarButton appearance="subtle" icon={<ArrowClockwise20Regular />} aria-label={nlsHPCC.Refresh} onClick={() => setRefreshToken(t => t + 1)}>
{nlsHPCC.Refresh}
</ToolbarButton>
</Toolbar>
}
main={
<SizeMe>{({ size }) => {
return <div style={{ position: "relative", width: "100%", height: "100%" }}>
<div style={{ position: "absolute", width: "100%", height: `${size.height}px`, overflowY: "auto" }}>
{
!isContainer ?
<>
<Divider>{nlsHPCC.DiskUsage}</Divider>
<DiskUsageCards refreshToken={refreshToken} />
</> :
<>
</>
}
<>
<Divider>{nlsHPCC.Activities}</Divider>
<QueueCards refreshToken={refreshToken} />
</>
</div>
</div>;
}}</SizeMe>
}
/>;
};
122 changes: 50 additions & 72 deletions esp/src/src-react/components/DiskUsage.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import * as React from "react";
import { Link } from "@fluentui/react";
import { MachineService } from "@hpcc-js/comms";
import { scopedLogger } from "@hpcc-js/util";
import { Divider, Toolbar, ToolbarButton } from "@fluentui/react-components";
import { ArrowClockwise20Regular } from "@fluentui/react-icons";
import { ComponentDetails as ComponentDetailsWidget, Summary as SummaryWidget } from "src/DiskUsage";
import nlsHPCC from "src/nlsHPCC";
import * as Utility from "src/Utility";
import { AutosizeHpccJSComponent } from "../layouts/HpccJSAdapter";
import { ReflexContainer, ReflexElement, ReflexSplitter, classNames, styles } from "../layouts/react-reflex";
import { HolyGrail } from "../layouts/HolyGrail";
import { SizeMe } from "../layouts/SizeMe";
import { pushUrl } from "../util/history";
import { FluentGrid, useFluentStoreState } from "./controls/Grid";

const logger = scopedLogger("src-react/components/DiskUsage.tsx");

const machineService = new MachineService({ baseUrl: "" });
import { FolderUsageCards } from "./cards/DiskUsageCard";
import { useTargetClusterUsageEx } from "../hooks/diskUsage";

interface SummaryProps {
cluster?: string;
Expand All @@ -34,15 +33,17 @@ export const Summary: React.FunctionComponent<SummaryProps> = ({
return <AutosizeHpccJSComponent widget={summary}></AutosizeHpccJSComponent >;
};

interface DetailsProps {
interface ClusterUsageProps {
cluster: string;
}

export const Details: React.FunctionComponent<DetailsProps> = ({
export const ClusterUsage: React.FunctionComponent<ClusterUsageProps> = ({
cluster
}) => {

const { refreshTable } = useFluentStoreState({});
const [refreshToken, setRefreshToken] = React.useState(0);
const { data: usage, refresh } = useTargetClusterUsageEx(cluster);

// Grid ---
const columns = React.useMemo(() => {
Expand Down Expand Up @@ -72,51 +73,48 @@ export const Details: React.FunctionComponent<DetailsProps> = ({

type Columns = typeof columns;
type Row = { __hpcc_id: string } & { [K in keyof Columns]: string | number };
const [data, setData] = React.useState<Row[]>([]);

const refreshData = React.useCallback(() => {
machineService.GetTargetClusterUsageEx([cluster])
.then(response => {
const _data: Row[] = [];
if (response) {
response.forEach(component => {
component.ComponentUsages.forEach(cu => {
cu.MachineUsages.forEach(mu => {
mu.DiskUsages.forEach((du, i) => {
_data.push({
__hpcc_id: `__usage_${i}`,
PercentUsed: Math.round((du.InUse / du.Total) * 100),
Component: cu.Name,
IPAddress: mu.Name,
Type: du.Name,
Path: du.Path,
InUse: Utility.convertedSize(du.InUse),
Total: Utility.convertedSize(du.Total)
});
});
});
const data = React.useMemo<Row[]>(() => {
const rows: Row[] = [];
(usage ?? []).forEach(component => {
component.ComponentUsages.forEach(cu => {
cu.MachineUsages.forEach(mu => {
mu.DiskUsages.forEach((du, i) => {
rows.push({
__hpcc_id: `__usage_${i}`,
PercentUsed: Math.round((du.InUse / du.Total) * 100),
Component: cu.Name,
IPAddress: mu.Name,
Type: du.Name,
Path: du.Path,
InUse: Utility.convertedSize(du.InUse),
Total: Utility.convertedSize(du.Total)
});
});
}
setData(_data);
})
.catch(err => logger.error(err))
;
}, [cluster]);

React.useEffect(() => {
refreshData();
}, [refreshData]);

return <FluentGrid
data={data}
primaryID={"__hpcc_id"}
sort={{ attribute: "__hpcc_id", descending: false }}
columns={columns}
setSelection={() => null}
setTotal={() => null}
refresh={refreshTable}
></FluentGrid>;
});
});
});
return rows;
}, [usage]);

return <HolyGrail
header={
<Toolbar>
<ToolbarButton appearance="subtle" icon={<ArrowClockwise20Regular />} aria-label={nlsHPCC.Refresh} onClick={() => { refresh(); setRefreshToken(t => t + 1); }}>
{nlsHPCC.Refresh}
</ToolbarButton>
</Toolbar>
}
main={<SizeMe>{({ size }) => {
return <div style={{ position: "relative", width: "100%", height: "100%" }}>
<div style={{ position: "absolute", width: "100%", height: `${size.height}px`, overflowY: "auto" }}>
<Divider>{nlsHPCC.Category}</Divider>
<FolderUsageCards cluster={cluster} refreshToken={refreshToken} />
<Divider>{nlsHPCC.Folders}</Divider>
<FluentGrid data={data} primaryID="__hpcc_id" sort={{ attribute: "__hpcc_id", descending: false }} columns={columns} setSelection={() => null} setTotal={() => null} refresh={refreshTable} />
</div>
</div>;
}}</SizeMe>}
/>;
};

interface MachineUsageProps {
Expand All @@ -135,23 +133,3 @@ export const MachineUsage: React.FunctionComponent<MachineUsageProps> = ({

return <AutosizeHpccJSComponent widget={summary}></AutosizeHpccJSComponent >;
};

interface ClusterUsageProps {
cluster: string;
}

export const ClusterUsage: React.FunctionComponent<ClusterUsageProps> = ({
cluster
}) => {
return <ReflexContainer orientation="horizontal">
<ReflexElement minSize={100} size={100} style={{ overflow: "hidden" }}>
<Summary cluster={cluster} />
</ReflexElement>
<ReflexSplitter style={styles.reflexSplitter}>
<div className={classNames.reflexSplitterDiv}></div>
</ReflexSplitter>
<ReflexElement style={{ overflow: "hidden" }}>
<Details cluster={cluster} />
</ReflexElement>
</ReflexContainer >;
};
1 change: 1 addition & 0 deletions esp/src/src-react/components/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ type SubMenuItems = { [nav: string]: SubMenu[] };
const subMenuItems: SubMenuItems = {
"activities": [
{ headerText: nlsHPCC.Activities, itemKey: "/activities" },
{ headerText: nlsHPCC.ActivitiesPreview, itemKey: "/activities-preview" },
{ headerText: nlsHPCC.EventScheduler, itemKey: "/events" }
],
"workunits": [
Expand Down
60 changes: 60 additions & 0 deletions esp/src/src-react/components/cards/CardGroup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import * as React from "react";
import { makeStyles, mergeClasses, tokens } from "@fluentui/react-components";

const useStyles = makeStyles({
root: {
display: "grid",
alignItems: "start",
alignContent: "start",
justifyContent: "start",
placeContent: "start",
width: "100%",
boxSizing: "border-box",
}
});

export interface CardGroupProps {
children?: React.ReactNode;
minColumnWidth?: number | string;
autoRows?: number | string;
columnGap?: string;
rowGap?: string;
paddingInline?: string;
paddingBlock?: string;
scrollY?: boolean;
className?: string;
style?: React.CSSProperties;
}

export const CardGroup: React.FunctionComponent<CardGroupProps> = ({
children,
minColumnWidth = 280,
autoRows = 320,
columnGap = tokens.spacingHorizontalM,
rowGap = tokens.spacingHorizontalM,
paddingInline = tokens.spacingHorizontalM,
paddingBlock = tokens.spacingVerticalM,
scrollY = false,
className,
style
}) => {
const styles = useStyles();

const toCssLen = (v: number | string) => typeof v === "number" ? `${v}px` : v;

const computedStyle: React.CSSProperties = {
gridTemplateColumns: `repeat(auto-fill, minmax(${toCssLen(minColumnWidth)}, 1fr))`,
columnGap,
rowGap,
paddingInline,
paddingBlock,
gridAutoRows: toCssLen(autoRows),
overflowY: scrollY ? "auto" : undefined,
minHeight: scrollY ? 0 : undefined,
...style
};

return <div className={mergeClasses(styles.root, className)} style={computedStyle}>
{children}
</div>;
};
Loading
Loading