1
1
import Link from "@material-ui/core/Link"
2
2
import { OutdatedHelpTooltip } from "components/Tooltips"
3
- import { FC } from "react"
3
+ import { FC , useRef , useState } from "react"
4
4
import { Link as RouterLink } from "react-router-dom"
5
5
import { createDayString } from "util/createDayString"
6
6
import {
@@ -12,6 +12,14 @@ import { Workspace } from "../../api/typesGenerated"
12
12
import { Stats , StatsItem } from "components/Stats/Stats"
13
13
import upperFirst from "lodash/upperFirst"
14
14
import { autostopDisplay } from "util/schedule"
15
+ import IconButton from "@material-ui/core/IconButton"
16
+ import RemoveIcon from "@material-ui/icons/RemoveOutlined"
17
+ import { makeStyles } from "@material-ui/core/styles"
18
+ import AddIcon from "@material-ui/icons/AddOutlined"
19
+ import Popover from "@material-ui/core/Popover"
20
+ import TextField from "@material-ui/core/TextField"
21
+ import { Stack } from "components/Stack/Stack"
22
+ import Button from "@material-ui/core/Button"
15
23
16
24
const Language = {
17
25
workspaceDetails : "Workspace Details" ,
@@ -27,77 +35,164 @@ const Language = {
27
35
28
36
export interface WorkspaceStatsProps {
29
37
workspace : Workspace
38
+ maxDeadlineIncrease : number
39
+ maxDeadlineDecrease : number
30
40
quota_budget ?: number
31
41
handleUpdate : ( ) => void
32
42
}
33
43
34
44
export const WorkspaceStats : FC < WorkspaceStatsProps > = ( {
35
45
workspace,
36
46
quota_budget,
47
+ maxDeadlineDecrease,
48
+ maxDeadlineIncrease,
37
49
handleUpdate,
38
50
} ) => {
39
51
const initiatedBy = getDisplayWorkspaceBuildInitiatedBy (
40
52
workspace . latest_build ,
41
53
)
42
54
const displayTemplateName = getDisplayWorkspaceTemplateName ( workspace )
55
+ const styles = useStyles ( )
56
+ const deadlinePlusEnabled = maxDeadlineIncrease >= 1
57
+ const deadlineMinusEnabled = maxDeadlineDecrease >= 1
58
+ const addingButtonRef = useRef < HTMLButtonElement > ( null )
59
+ const [ isAddingTime , setIsAddingTime ] = useState ( false )
43
60
44
61
return (
45
- < Stats aria-label = { Language . workspaceDetails } >
46
- < StatsItem
47
- label = { Language . templateLabel }
48
- value = {
49
- < Link
50
- component = { RouterLink }
51
- to = { `/templates/${ workspace . template_name } ` }
52
- >
53
- { displayTemplateName }
54
- </ Link >
55
- }
56
- />
57
- < StatsItem
58
- label = { Language . versionLabel }
59
- value = {
60
- < >
62
+ < >
63
+ < Stats aria-label = { Language . workspaceDetails } >
64
+ < StatsItem
65
+ label = { Language . templateLabel }
66
+ value = {
61
67
< Link
62
68
component = { RouterLink }
63
- to = { `/templates/${ workspace . template_name } /versions/ ${ workspace . latest_build . template_version_name } ` }
69
+ to = { `/templates/${ workspace . template_name } ` }
64
70
>
65
- { workspace . latest_build . template_version_name }
71
+ { displayTemplateName }
66
72
</ Link >
67
-
68
- { workspace . outdated && (
69
- < OutdatedHelpTooltip
70
- onUpdateVersion = { handleUpdate }
71
- ariaLabel = "update version"
72
- />
73
- ) }
74
- </ >
75
- }
76
- />
77
- < StatsItem
78
- label = { Language . lastBuiltLabel }
79
- value = {
80
- < >
81
- { upperFirst ( createDayString ( workspace . latest_build . created_at ) ) } by{ " " }
82
- { initiatedBy }
83
- </ >
84
- }
85
- />
86
- { shouldDisplayScheduleLabel ( workspace ) && (
73
+ }
74
+ />
87
75
< StatsItem
88
- label = { getScheduleLabel ( workspace ) }
89
- value = { autostopDisplay ( workspace ) }
76
+ label = { Language . versionLabel }
77
+ value = {
78
+ < >
79
+ < Link
80
+ component = { RouterLink }
81
+ to = { `/templates/${ workspace . template_name } /versions/${ workspace . latest_build . template_version_name } ` }
82
+ >
83
+ { workspace . latest_build . template_version_name }
84
+ </ Link >
85
+
86
+ { workspace . outdated && (
87
+ < OutdatedHelpTooltip
88
+ onUpdateVersion = { handleUpdate }
89
+ ariaLabel = "update version"
90
+ />
91
+ ) }
92
+ </ >
93
+ }
90
94
/>
91
- ) }
92
- { workspace . latest_build . daily_cost > 0 && (
93
95
< StatsItem
94
- label = { Language . costLabel }
95
- value = { `${ workspace . latest_build . daily_cost } ${
96
- quota_budget ? `/ ${ quota_budget } ` : ""
97
- } `}
96
+ label = { Language . lastBuiltLabel }
97
+ value = {
98
+ < >
99
+ { upperFirst ( createDayString ( workspace . latest_build . created_at ) ) } { " " }
100
+ by { initiatedBy }
101
+ </ >
102
+ }
98
103
/>
99
- ) }
100
- </ Stats >
104
+ { shouldDisplayScheduleLabel ( workspace ) && (
105
+ < StatsItem
106
+ label = { getScheduleLabel ( workspace ) }
107
+ value = {
108
+ < span className = { styles . scheduleValue } >
109
+ < Link
110
+ component = { RouterLink }
111
+ to = "settings/schedule"
112
+ title = "Schedule settings"
113
+ >
114
+ { autostopDisplay ( workspace ) }
115
+ </ Link >
116
+ < span className = { styles . scheduleControls } >
117
+ < IconButton
118
+ disabled = { ! deadlineMinusEnabled }
119
+ size = "small"
120
+ title = "Subtract hours from deadline"
121
+ className = { styles . scheduleButton }
122
+ >
123
+ < RemoveIcon />
124
+ </ IconButton >
125
+ < IconButton
126
+ disabled = { ! deadlinePlusEnabled }
127
+ size = "small"
128
+ title = "Add hours to deadline"
129
+ className = { styles . scheduleButton }
130
+ ref = { addingButtonRef }
131
+ onClick = { ( ) => setIsAddingTime ( true ) }
132
+ >
133
+ < AddIcon />
134
+ </ IconButton >
135
+ </ span >
136
+ </ span >
137
+ }
138
+ />
139
+ ) }
140
+ { workspace . latest_build . daily_cost > 0 && (
141
+ < StatsItem
142
+ label = { Language . costLabel }
143
+ value = { `${ workspace . latest_build . daily_cost } ${
144
+ quota_budget ? `/ ${ quota_budget } ` : ""
145
+ } `}
146
+ />
147
+ ) }
148
+ </ Stats >
149
+
150
+ < Popover
151
+ id = "schedule-add"
152
+ classes = { { paper : styles . timePopoverPaper } }
153
+ open = { isAddingTime }
154
+ anchorEl = { addingButtonRef . current }
155
+ onClose = { ( ) => setIsAddingTime ( false ) }
156
+ anchorOrigin = { {
157
+ vertical : "bottom" ,
158
+ horizontal : "right" ,
159
+ } }
160
+ transformOrigin = { {
161
+ vertical : "top" ,
162
+ horizontal : "right" ,
163
+ } }
164
+ >
165
+ < span className = { styles . timePopoverTitle } > Add hours to deadline</ span >
166
+ < span className = { styles . timePopoverDescription } >
167
+ Delay the shutdown of this workspace for a few more hours. This is
168
+ only applied once.
169
+ </ span >
170
+ < form className = { styles . timePopoverForm } >
171
+ < TextField
172
+ type = "number"
173
+ size = "small"
174
+ fullWidth
175
+ className = { styles . timePopoverField }
176
+ InputProps = { { className : styles . timePopoverFieldInput } }
177
+ inputProps = { {
178
+ min : 0 ,
179
+ max : maxDeadlineIncrease ,
180
+ step : 1 ,
181
+ defaultValue : 1 ,
182
+ } }
183
+ />
184
+
185
+ < Button
186
+ variant = "outlined"
187
+ size = "small"
188
+ className = { styles . timePopoverButton }
189
+ type = "submit"
190
+ >
191
+ Apply
192
+ </ Button >
193
+ </ form >
194
+ </ Popover >
195
+ </ >
101
196
)
102
197
}
103
198
@@ -118,3 +213,69 @@ export const shouldDisplayScheduleLabel = (workspace: Workspace): boolean => {
118
213
const getScheduleLabel = ( workspace : Workspace ) => {
119
214
return isWorkspaceOn ( workspace ) ? "Stops at" : "Starts at"
120
215
}
216
+
217
+ const useStyles = makeStyles ( ( theme ) => ( {
218
+ scheduleValue : {
219
+ display : "flex" ,
220
+ alignItems : "center" ,
221
+ gap : theme . spacing ( 1.5 ) ,
222
+ } ,
223
+
224
+ scheduleControls : {
225
+ display : "flex" ,
226
+ alignItems : "center" ,
227
+ gap : theme . spacing ( 0.5 ) ,
228
+ } ,
229
+
230
+ scheduleButton : {
231
+ border : `1px solid ${ theme . palette . divider } ` ,
232
+ borderRadius : 4 ,
233
+
234
+ "& svg.MuiSvgIcon-root" : {
235
+ width : theme . spacing ( 1.5 ) ,
236
+ height : theme . spacing ( 1.5 ) ,
237
+ } ,
238
+ } ,
239
+
240
+ timePopoverPaper : {
241
+ padding : theme . spacing ( 3 ) ,
242
+ maxWidth : theme . spacing ( 36 ) ,
243
+ marginTop : theme . spacing ( 1 ) ,
244
+ borderRadius : 4 ,
245
+ display : "flex" ,
246
+ flexDirection : "column" ,
247
+ gap : theme . spacing ( 1 ) ,
248
+ } ,
249
+
250
+ timePopoverTitle : {
251
+ fontWeight : 600 ,
252
+ } ,
253
+
254
+ timePopoverDescription : {
255
+ color : theme . palette . text . secondary ,
256
+ } ,
257
+
258
+ timePopoverForm : {
259
+ display : "flex" ,
260
+ alignItems : "center" ,
261
+ gap : theme . spacing ( 1 ) ,
262
+ padding : theme . spacing ( 1 , 0 ) ,
263
+ } ,
264
+
265
+ timePopoverField : {
266
+ margin : 0 ,
267
+ } ,
268
+
269
+ timePopoverFieldInput : {
270
+ fontSize : 14 ,
271
+ padding : theme . spacing ( 0 ) ,
272
+ borderRadius : 4 ,
273
+ } ,
274
+
275
+ timePopoverButton : {
276
+ borderRadius : 4 ,
277
+ paddingLeft : theme . spacing ( 2 ) ,
278
+ paddingRight : theme . spacing ( 2 ) ,
279
+ flexShrink : 0 ,
280
+ } ,
281
+ } ) )
0 commit comments