mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-19 17:01:13 +00:00
More Spark UI Cleanup (#89)
Co-authored-by: Steve Faulkner <stfaul@microsoft.com>
This commit is contained in:
@@ -1,40 +0,0 @@
|
||||
import * as React from "react";
|
||||
import { DetailsList, IColumn, SelectionMode } from "office-ui-fabric-react/lib/DetailsList";
|
||||
import { Library } from "../../../Contracts/DataModels";
|
||||
|
||||
export interface ClusterLibraryItem extends Library {
|
||||
installed: boolean;
|
||||
}
|
||||
|
||||
export interface ClusterLibraryGridProps {
|
||||
libraryItems: ClusterLibraryItem[];
|
||||
onInstalledChanged: (libraryName: string, installed: boolean) => void;
|
||||
}
|
||||
|
||||
export function ClusterLibraryGrid(props: ClusterLibraryGridProps): JSX.Element {
|
||||
const onInstalledChanged = (e: React.FormEvent<HTMLInputElement>) => {
|
||||
const target = e.target;
|
||||
const libraryName = (target as any).dataset.name;
|
||||
const checked = (target as any).checked;
|
||||
return props.onInstalledChanged(libraryName, checked);
|
||||
};
|
||||
|
||||
const columns: IColumn[] = [
|
||||
{
|
||||
key: "name",
|
||||
name: "Name",
|
||||
fieldName: "name",
|
||||
minWidth: 150
|
||||
},
|
||||
{
|
||||
key: "installed",
|
||||
name: "Installed",
|
||||
minWidth: 100,
|
||||
onRender: (item: ClusterLibraryItem) => {
|
||||
return <input type="checkbox" checked={item.installed} onChange={onInstalledChanged} data-name={item.name} />;
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
return <DetailsList columns={columns} items={props.libraryItems} selectionMode={SelectionMode.none} />;
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import * as React from "react";
|
||||
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
|
||||
import { ClusterLibraryGrid, ClusterLibraryGridProps } from "./ClusterLibraryGrid";
|
||||
|
||||
export class ClusterLibraryGridAdapter implements ReactAdapter {
|
||||
public parameters: ko.Observable<ClusterLibraryGridProps>;
|
||||
|
||||
public renderComponent(): JSX.Element {
|
||||
return <ClusterLibraryGrid {...this.parameters()} />;
|
||||
}
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
import * as React from "react";
|
||||
import DeleteIcon from "../../../../images/delete.svg";
|
||||
import { Button } from "office-ui-fabric-react/lib/Button";
|
||||
import { DetailsList, IColumn, SelectionMode } from "office-ui-fabric-react/lib/DetailsList";
|
||||
import { Library } from "../../../Contracts/DataModels";
|
||||
import { Label } from "office-ui-fabric-react/lib/Label";
|
||||
import { SparkLibrary } from "../../../Common/Constants";
|
||||
import { TextField } from "office-ui-fabric-react/lib/TextField";
|
||||
|
||||
export interface LibraryManageComponentProps {
|
||||
addProps: {
|
||||
nameProps: LibraryAddNameTextFieldProps;
|
||||
urlProps: LibraryAddUrlTextFieldProps;
|
||||
buttonProps: LibraryAddButtonProps;
|
||||
};
|
||||
gridProps: LibraryManageGridProps;
|
||||
}
|
||||
|
||||
export function LibraryManageComponent(props: LibraryManageComponentProps): JSX.Element {
|
||||
const {
|
||||
addProps: { nameProps, urlProps, buttonProps },
|
||||
gridProps
|
||||
} = props;
|
||||
return (
|
||||
<div>
|
||||
<div className="library-add-container">
|
||||
<LibraryAddNameTextField {...nameProps} />
|
||||
<LibraryAddUrlTextField {...urlProps} />
|
||||
<LibraryAddButton {...buttonProps} />
|
||||
</div>
|
||||
<div className="library-grid-container">
|
||||
<Label>All Libraries</Label>
|
||||
<LibraryManageGrid {...gridProps} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export interface LibraryManageGridProps {
|
||||
items: Library[];
|
||||
onLibraryDeleteClick: (libraryName: string) => void;
|
||||
}
|
||||
|
||||
function LibraryManageGrid(props: LibraryManageGridProps): JSX.Element {
|
||||
const columns: IColumn[] = [
|
||||
{
|
||||
key: "name",
|
||||
name: "Name",
|
||||
fieldName: "name",
|
||||
minWidth: 200
|
||||
},
|
||||
{
|
||||
key: "delete",
|
||||
name: "Delete",
|
||||
minWidth: 60,
|
||||
onRender: (item: Library) => {
|
||||
const onDelete = () => {
|
||||
props.onLibraryDeleteClick(item.name);
|
||||
};
|
||||
return (
|
||||
<span className="library-delete">
|
||||
<img src={DeleteIcon} alt="Delete" onClick={onDelete} />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
];
|
||||
return <DetailsList columns={columns} items={props.items} selectionMode={SelectionMode.none} />;
|
||||
}
|
||||
|
||||
export interface LibraryAddButtonProps {
|
||||
disabled: boolean;
|
||||
onLibraryAddClick: (event: React.FormEvent<any>) => void;
|
||||
}
|
||||
|
||||
function LibraryAddButton(props: LibraryAddButtonProps): JSX.Element {
|
||||
return (
|
||||
<Button text="Add" className="library-add-button" onClick={props.onLibraryAddClick} disabled={props.disabled} />
|
||||
);
|
||||
}
|
||||
|
||||
export interface LibraryAddUrlTextFieldProps {
|
||||
libraryAddress: string;
|
||||
onLibraryAddressChange: (libraryAddress: string) => void;
|
||||
onLibraryAddressValidated: (errorMessage: string, value: string) => void;
|
||||
}
|
||||
|
||||
function LibraryAddUrlTextField(props: LibraryAddUrlTextFieldProps): JSX.Element {
|
||||
const handleTextChange = (e: React.FormEvent<any>, libraryAddress: string) => {
|
||||
props.onLibraryAddressChange(libraryAddress);
|
||||
};
|
||||
const validateText = (text: string): string => {
|
||||
if (!text) {
|
||||
return "";
|
||||
}
|
||||
const libraryUrlRegex = /^(https:\/\/.+\/)(.+)\.(jar)$/gi;
|
||||
const isValidUrl = libraryUrlRegex.test(text);
|
||||
if (isValidUrl) {
|
||||
return "";
|
||||
}
|
||||
return "Need to be a valid https uri";
|
||||
};
|
||||
return (
|
||||
<TextField
|
||||
value={props.libraryAddress}
|
||||
label="Url"
|
||||
type="url"
|
||||
className="library-add-textfield"
|
||||
onChange={handleTextChange}
|
||||
onGetErrorMessage={validateText}
|
||||
onNotifyValidationResult={props.onLibraryAddressValidated}
|
||||
placeholder="https://myrepo/myjar.jar"
|
||||
autoComplete="off"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export interface LibraryAddNameTextFieldProps {
|
||||
libraryName: string;
|
||||
onLibraryNameChange: (libraryName: string) => void;
|
||||
onLibraryNameValidated: (errorMessage: string, value: string) => void;
|
||||
}
|
||||
|
||||
function LibraryAddNameTextField(props: LibraryAddNameTextFieldProps): JSX.Element {
|
||||
const handleTextChange = (e: React.FormEvent<any>, libraryName: string) => {
|
||||
props.onLibraryNameChange(libraryName);
|
||||
};
|
||||
const validateText = (text: string): string => {
|
||||
if (!text) {
|
||||
return "";
|
||||
}
|
||||
const length = text.length;
|
||||
if (length < SparkLibrary.nameMinLength || length > SparkLibrary.nameMaxLength) {
|
||||
return "Library name length need to be between 3 and 63.";
|
||||
}
|
||||
const nameRegex = /^[a-z0-9][-a-z0-9]*[a-z0-9]$/gi;
|
||||
const isValidUrl = nameRegex.test(text);
|
||||
if (isValidUrl) {
|
||||
return "";
|
||||
}
|
||||
return "Need to be a valid name. Letters, numbers and - are allowed";
|
||||
};
|
||||
return (
|
||||
<TextField
|
||||
value={props.libraryName}
|
||||
label="Name"
|
||||
type="text"
|
||||
className="library-add-textfield"
|
||||
onChange={handleTextChange}
|
||||
onGetErrorMessage={validateText}
|
||||
onNotifyValidationResult={props.onLibraryNameValidated}
|
||||
placeholder="myjar"
|
||||
autoComplete="off"
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import * as React from "react";
|
||||
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
|
||||
import { LibraryManageComponent, LibraryManageComponentProps } from "./LibraryManage";
|
||||
|
||||
export class LibraryManageComponentAdapter implements ReactAdapter {
|
||||
public parameters: ko.Observable<LibraryManageComponentProps>;
|
||||
|
||||
public renderComponent(): JSX.Element {
|
||||
return <LibraryManageComponent {...this.parameters()} />;
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
@import "../../../../less/Common/Constants";
|
||||
|
||||
.labelWithRedAsterisk {
|
||||
line-height: 18px;
|
||||
font-size: @DefaultFontSize;
|
||||
font-family: @DataExplorerFont;
|
||||
color: @DefaultFontColor;
|
||||
}
|
||||
|
||||
.labelWithRedAsterisk::before {
|
||||
content: "* ";
|
||||
color: @SelectionHigh;
|
||||
}
|
||||
|
||||
.clusterSettingsDropdown {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
@@ -1,159 +0,0 @@
|
||||
import * as React from "react";
|
||||
import { Dropdown, IDropdownOption, IDropdownProps } from "office-ui-fabric-react/lib/Dropdown";
|
||||
import { Slider, ISliderProps } from "office-ui-fabric-react/lib/Slider";
|
||||
import { Stack, IStackItemStyles, IStackStyles } from "office-ui-fabric-react/lib/Stack";
|
||||
import { TextField, ITextFieldProps } from "office-ui-fabric-react/lib/TextField";
|
||||
import { Spark } from "../../../Common/Constants";
|
||||
import { SparkCluster } from "../../../Contracts/DataModels";
|
||||
|
||||
export interface ClusterSettingsComponentProps {
|
||||
cluster: SparkCluster;
|
||||
onClusterSettingsChanged: (cluster: SparkCluster) => void;
|
||||
}
|
||||
|
||||
export class ClusterSettingsComponent extends React.Component<ClusterSettingsComponentProps, {}> {
|
||||
constructor(props: ClusterSettingsComponentProps) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
return (
|
||||
<>
|
||||
{this.getMasterSizeDropdown()}
|
||||
{this.getWorkerSizeDropdown()}
|
||||
{this.getWorkerCountSliderInput()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
private getMasterSizeDropdown(): JSX.Element {
|
||||
const driverSize: string =
|
||||
this.props.cluster && this.props.cluster.properties && this.props.cluster.properties.driverSize;
|
||||
const masterSizeOptions: IDropdownOption[] = Spark.SKUs.keys().map(sku => ({
|
||||
key: sku,
|
||||
text: Spark.SKUs.get(sku)
|
||||
}));
|
||||
const masterSizeDropdownProps: IDropdownProps = {
|
||||
label: "Master Size",
|
||||
options: masterSizeOptions,
|
||||
defaultSelectedKey: driverSize,
|
||||
onChange: this._onDriverSizeChange,
|
||||
styles: {
|
||||
root: "clusterSettingsDropdown"
|
||||
}
|
||||
};
|
||||
return <Dropdown {...masterSizeDropdownProps} />;
|
||||
}
|
||||
|
||||
private getWorkerSizeDropdown(): JSX.Element {
|
||||
const workerSize: string =
|
||||
this.props.cluster && this.props.cluster.properties && this.props.cluster.properties.workerSize;
|
||||
const workerSizeOptions: IDropdownOption[] = Spark.SKUs.keys().map(sku => ({
|
||||
key: sku,
|
||||
text: Spark.SKUs.get(sku)
|
||||
}));
|
||||
const workerSizeDropdownProps: IDropdownProps = {
|
||||
label: "Worker Size",
|
||||
options: workerSizeOptions,
|
||||
defaultSelectedKey: workerSize,
|
||||
onChange: this._onWorkerSizeChange,
|
||||
styles: {
|
||||
label: "labelWithRedAsterisk",
|
||||
root: "clusterSettingsDropdown"
|
||||
}
|
||||
};
|
||||
return <Dropdown {...workerSizeDropdownProps} />;
|
||||
}
|
||||
|
||||
private getWorkerCountSliderInput(): JSX.Element {
|
||||
const workerCount: number =
|
||||
(this.props.cluster &&
|
||||
this.props.cluster.properties &&
|
||||
this.props.cluster.properties.workerInstanceCount !== undefined &&
|
||||
this.props.cluster.properties.workerInstanceCount) ||
|
||||
0;
|
||||
const stackStyle: IStackStyles = {
|
||||
root: {
|
||||
paddingTop: 5
|
||||
}
|
||||
};
|
||||
const sliderItemStyle: IStackItemStyles = {
|
||||
root: {
|
||||
width: "100%",
|
||||
paddingRight: 20
|
||||
}
|
||||
};
|
||||
|
||||
const workerCountSliderProps: ISliderProps = {
|
||||
min: 0,
|
||||
max: Spark.MaxWorkerCount,
|
||||
step: 1,
|
||||
value: workerCount,
|
||||
showValue: false,
|
||||
onChange: this._onWorkerCountChange,
|
||||
styles: {
|
||||
root: {
|
||||
width: "100%",
|
||||
paddingRight: 20
|
||||
}
|
||||
}
|
||||
};
|
||||
const workerCountTextFieldProps: ITextFieldProps = {
|
||||
value: workerCount.toString(),
|
||||
styles: {
|
||||
fieldGroup: {
|
||||
width: 45,
|
||||
height: 25
|
||||
},
|
||||
field: {
|
||||
textAlign: "center"
|
||||
}
|
||||
},
|
||||
onChange: this._onWorkerCountTextFieldChange
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack styles={stackStyle}>
|
||||
<span className="labelWithRedAsterisk">Worker Nodes</span>
|
||||
<Stack horizontal verticalAlign="center">
|
||||
<Slider {...workerCountSliderProps} />
|
||||
<TextField {...workerCountTextFieldProps} />
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
private _onDriverSizeChange = (_event: React.FormEvent, selectedOption: IDropdownOption) => {
|
||||
const newValue: string = selectedOption.key as string;
|
||||
const cluster = this.props.cluster;
|
||||
if (cluster) {
|
||||
cluster.properties.driverSize = newValue;
|
||||
this.props.onClusterSettingsChanged(cluster);
|
||||
}
|
||||
};
|
||||
|
||||
private _onWorkerSizeChange = (_event: React.FormEvent, selectedOption: IDropdownOption) => {
|
||||
const newValue: string = selectedOption.key as string;
|
||||
const cluster = this.props.cluster;
|
||||
if (cluster) {
|
||||
cluster.properties.workerSize = newValue;
|
||||
this.props.onClusterSettingsChanged(cluster);
|
||||
}
|
||||
};
|
||||
|
||||
private _onWorkerCountChange = (count: number) => {
|
||||
count = Math.min(count, Spark.MaxWorkerCount);
|
||||
const cluster = this.props.cluster;
|
||||
if (cluster) {
|
||||
cluster.properties.workerInstanceCount = count;
|
||||
this.props.onClusterSettingsChanged(cluster);
|
||||
}
|
||||
};
|
||||
|
||||
private _onWorkerCountTextFieldChange = (_event: React.FormEvent, newValue: string) => {
|
||||
const count = parseInt(newValue);
|
||||
if (!isNaN(count)) {
|
||||
this._onWorkerCountChange(count);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import * as React from "react";
|
||||
import { ClusterSettingsComponent, ClusterSettingsComponentProps } from "./ClusterSettingsComponent";
|
||||
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
|
||||
|
||||
export class ClusterSettingsComponentAdapter implements ReactAdapter {
|
||||
public parameters: ko.Observable<ClusterSettingsComponentProps>;
|
||||
|
||||
public renderComponent(): JSX.Element {
|
||||
return <ClusterSettingsComponent {...this.parameters()} />;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user