Skip to content

Commit 1869131

Browse files
Pablo Carmonastarpit
authored andcommitted
feat: enhance ui profiles/appliances section
1 parent 5302e4b commit 1869131

File tree

5 files changed

+357
-48
lines changed

5 files changed

+357
-48
lines changed
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* Copyright 2022 The Kubernetes Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import React from "react"
18+
import { openWindow } from "../controller/profile/actions"
19+
import { Select, SelectOption, SelectOptionObject, SelectVariant } from "@patternfly/react-core"
20+
21+
type Props = {
22+
selectedProfile?: string
23+
}
24+
25+
type State = {
26+
selectIsOpen: boolean
27+
}
28+
29+
export default class DashboardSelect extends React.PureComponent<Props, State> {
30+
public constructor(props: Props) {
31+
super(props)
32+
this.state = {
33+
selectIsOpen: false,
34+
}
35+
}
36+
private readonly _dashboardSelectOnToggle = this.dashboardSelectOnToggle.bind(this)
37+
private readonly _dashboardSelectOnSelect = this.dashboardSelectOnSelect.bind(this)
38+
39+
dashboardSelectOnToggle(selectIsOpen: boolean) {
40+
this.setState({ selectIsOpen })
41+
}
42+
43+
async dashboardSelectOnSelect(
44+
event: React.ChangeEvent<Element> | React.MouseEvent<Element>,
45+
selection: string | SelectOptionObject,
46+
isPlaceholder?: boolean | undefined
47+
) {
48+
if (isPlaceholder) {
49+
this.setState({ selectIsOpen: false })
50+
} else {
51+
openWindow(`Codeflare Run Summary - ${this.props.selectedProfile}`, "Codeflare Run Summary", [
52+
"codeflare",
53+
"get",
54+
"run",
55+
"--profile",
56+
this.props.selectedProfile,
57+
])
58+
this.setState({ selectIsOpen: false })
59+
}
60+
}
61+
62+
public render() {
63+
return (
64+
<Select
65+
variant={SelectVariant.single}
66+
placeholderText="Dashboards"
67+
aria-label="Dashboards selector"
68+
onToggle={this._dashboardSelectOnToggle}
69+
onSelect={this._dashboardSelectOnSelect}
70+
isOpen={this.state.selectIsOpen}
71+
aria-labelledby="select-dashboard-label"
72+
>
73+
<SelectOption value="CodeFlare" />
74+
<SelectOption value="MLFlow" isPlaceholder />
75+
<SelectOption value="Tensorboard" isPlaceholder />
76+
</Select>
77+
)
78+
}
79+
}

plugins/plugin-codeflare/src/components/ProfileExplorer.tsx

Lines changed: 95 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,31 @@
1515
*/
1616

1717
import React from "react"
18-
import prettyMillis from "pretty-ms"
1918
import { EventEmitter } from "events"
2019
import { Profiles } from "madwizard"
2120
import { Loading } from "@kui-shell/plugin-client-common"
22-
import { Grid, GridItem, Tile } from "@patternfly/react-core"
23-
21+
import {
22+
Card,
23+
CardTitle,
24+
CardBody,
25+
Title,
26+
Button,
27+
Flex,
28+
FlexItem,
29+
CardFooter,
30+
DescriptionList,
31+
DescriptionListGroup,
32+
DescriptionListTerm,
33+
DescriptionListDescription,
34+
} from "@patternfly/react-core"
35+
36+
import ProfileSelect from "./ProfileSelect"
37+
import DashboardSelect from "./DashboardSelect"
2438
import ProfileWatcher from "../tray/watchers/profile/list"
39+
import { handleBoot, handleShutdown } from "../controller/profile/actions"
2540

26-
import PlusIcon from "@patternfly/react-icons/dist/esm/icons/user-plus-icon"
27-
// import ProfileIcon from "@patternfly/react-icons/dist/esm/icons/user-icon"
41+
import "../../web/scss/components/Dashboard/Description.scss"
42+
import "../../web/scss/components/ProfileExplorer/_index.scss"
2843

2944
const events = new EventEmitter()
3045

@@ -55,6 +70,9 @@ export default class ProfileExplorer extends React.PureComponent<Props, State> {
5570
this.init()
5671
}
5772

73+
private readonly _handleBoot = handleBoot.bind(this)
74+
private readonly _handleShutdown = handleShutdown.bind(this)
75+
5876
private updateDebouncer: null | ReturnType<typeof setTimeout> = null
5977

6078
private readonly updateFn = () => {
@@ -113,56 +131,85 @@ export default class ProfileExplorer extends React.PureComponent<Props, State> {
113131
}
114132
}
115133

116-
/** User has clicked to select a profile */
117-
private readonly onSelect = async (evt: React.MouseEvent<HTMLElement>) => {
118-
const selectedProfile = evt.currentTarget.getAttribute("data-profile")
119-
evt.currentTarget.scrollIntoView(true)
120-
if (selectedProfile && selectedProfile !== this.state.selectedProfile) {
121-
if (await Profiles.bumpLastUsedTime(selectedProfile)) {
122-
emitSelectProfile(selectedProfile)
123-
this.setState({ selectedProfile })
124-
}
125-
}
126-
}
127-
128-
private prettyMillis(duration: number) {
129-
if (duration < 1000) {
130-
return "just now"
131-
} else {
132-
return prettyMillis(duration, { compact: true }) + " ago"
133-
}
134-
}
135-
136134
public render() {
137135
if (this.state && this.state.catastrophicError) {
138136
return "Internal Error"
139137
} else if (!this.state || !this.state.profiles) {
140138
return <Loading />
141139
} else {
142140
return (
143-
<Grid className="codeflare--gallery-grid flex-fill sans-serif top-pad left-pad right-pad bottom-pad" hasGutter>
144-
{this.state.profiles.map((_) => (
145-
<GridItem key={_.name}>
146-
<Tile
147-
className="codeflare--tile"
148-
data-profile={_.name}
149-
title={_.name}
150-
isSelected={this.state.selectedProfile === _.name}
151-
onClick={this.onSelect}
152-
>
153-
{`Last used ${this.prettyMillis(Date.now() - _.lastUsedTime)}`}
154-
</Tile>
155-
</GridItem>
156-
))}
157-
158-
{
159-
<GridItem>
160-
<Tile className="codeflare--tile codeflare--tile-new" title="New Profile" icon={<PlusIcon />} isDisabled>
161-
Customize a profile
162-
</Tile>
163-
</GridItem>
164-
}
165-
</Grid>
141+
<Flex className="codeflare--profile-explorer flex-fill" direction={{ default: "column" }}>
142+
<FlexItem>
143+
<ProfileSelect selectedProfile={this.state.selectedProfile} profiles={this.state.profiles} />
144+
</FlexItem>
145+
146+
<FlexItem>
147+
<Card className="top-pad left-pad right-pad bottompad">
148+
<CardTitle>
149+
<Flex>
150+
<FlexItem flex={{ default: "flex_1" }}>
151+
<Title headingLevel="h2" size="md">
152+
{this.state.selectedProfile}
153+
</Title>
154+
</FlexItem>
155+
<FlexItem>
156+
<Title headingLevel="h2" size="md">
157+
Status: pending
158+
</Title>
159+
</FlexItem>
160+
</Flex>
161+
</CardTitle>
162+
<CardBody>
163+
{/* TODO: Retrieve real data and abstract to its own component */}
164+
<DescriptionList className="codeflare--profile-explorer--description">
165+
<DescriptionListGroup className="codeflare--profile-explorer--description--group">
166+
<DescriptionListTerm>Cluster Context</DescriptionListTerm>
167+
<DescriptionListDescription>
168+
api-codeflare-train-v11-codeflare-openshift-com
169+
</DescriptionListDescription>
170+
</DescriptionListGroup>
171+
<DescriptionListGroup className="codeflare--profile-explorer--description--group">
172+
<DescriptionListTerm>Cluster Namespace</DescriptionListTerm>
173+
<DescriptionListDescription>nvidia-gpu-operator</DescriptionListDescription>
174+
</DescriptionListGroup>
175+
<DescriptionListGroup className="codeflare--profile-explorer--description--group">
176+
<DescriptionListTerm>Memory per Worker</DescriptionListTerm>
177+
<DescriptionListDescription>32Gi</DescriptionListDescription>
178+
</DescriptionListGroup>
179+
<DescriptionListGroup className="codeflare--profile-explorer--description--group">
180+
<DescriptionListTerm>Worker Count</DescriptionListTerm>
181+
<DescriptionListDescription>4-4</DescriptionListDescription>
182+
</DescriptionListGroup>
183+
</DescriptionList>
184+
</CardBody>
185+
<CardFooter>
186+
<Flex>
187+
<FlexItem>
188+
<Button
189+
variant="primary"
190+
className="codeflare--profile-explorer--boot-btn"
191+
onClick={() => this._handleBoot(this.state.selectedProfile)}
192+
>
193+
Boot
194+
</Button>
195+
</FlexItem>
196+
<FlexItem>
197+
<Button
198+
variant="secondary"
199+
className="codeflare--profile-explorer--shutdown-btn"
200+
onClick={() => this._handleShutdown(this.state.selectedProfile)}
201+
>
202+
Shutdown
203+
</Button>
204+
</FlexItem>
205+
<FlexItem>
206+
<DashboardSelect selectedProfile={this.state.selectedProfile} />
207+
</FlexItem>
208+
</Flex>
209+
</CardFooter>
210+
</Card>
211+
</FlexItem>
212+
</Flex>
166213
)
167214
}
168215
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*
2+
* Copyright 2022 The Kubernetes Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import React from "react"
18+
import prettyMillis from "pretty-ms"
19+
import { Profiles } from "madwizard"
20+
import { Select, SelectOption, SelectOptionObject, SelectVariant } from "@patternfly/react-core"
21+
import { UserIcon } from "@patternfly/react-icons"
22+
23+
type Props = {
24+
selectedProfile?: string
25+
profiles?: Profiles.Profile[]
26+
}
27+
28+
type State = {
29+
selectIsOpen: boolean
30+
selectedProfile?: string
31+
selectDefaultOption?: string | SelectOptionObject
32+
}
33+
34+
export default class ProfileSelect extends React.PureComponent<Props, State> {
35+
public constructor(props: Props) {
36+
super(props)
37+
this.state = {
38+
selectIsOpen: false,
39+
selectedProfile: props.selectedProfile,
40+
}
41+
}
42+
43+
private readonly _selectOnToggle = this.selectOnToggle.bind(this)
44+
private readonly _selectOnSelect = this.selectOnSelect.bind(this)
45+
46+
private prettyMillis(duration: number) {
47+
if (duration < 1000) {
48+
return "just now"
49+
} else {
50+
return prettyMillis(duration, { compact: true }) + " ago"
51+
}
52+
}
53+
54+
private selectOnToggle(selectIsOpen: boolean) {
55+
this.setState({ selectIsOpen })
56+
}
57+
58+
private selectOnSelect(
59+
event: React.ChangeEvent<Element> | React.MouseEvent<Element>,
60+
selection: string | SelectOptionObject,
61+
isPlaceholder?: boolean | undefined
62+
) {
63+
if (isPlaceholder) {
64+
this.clearSelection()
65+
} else {
66+
this.setState({
67+
selectDefaultOption: selection,
68+
selectIsOpen: false,
69+
})
70+
}
71+
}
72+
73+
private clearSelection() {
74+
this.setState({
75+
selectDefaultOption: undefined,
76+
selectIsOpen: false,
77+
})
78+
}
79+
80+
public render() {
81+
return (
82+
<Select
83+
toggleIcon={<UserIcon />}
84+
variant={SelectVariant.single}
85+
placeholderText="Select a profile"
86+
aria-label="Profiles selector with description"
87+
onToggle={this._selectOnToggle}
88+
onSelect={this._selectOnSelect}
89+
selections={this.props.selectedProfile}
90+
isOpen={this.state.selectIsOpen}
91+
aria-labelledby="select-profile-label"
92+
>
93+
{this.props.profiles?.map((profile, index) => (
94+
<SelectOption
95+
key={index}
96+
value={profile.name}
97+
description={`Last used ${this.prettyMillis(Date.now() - profile.lastUsedTime)}`}
98+
/>
99+
))}
100+
</Select>
101+
)
102+
}
103+
}

0 commit comments

Comments
 (0)