Skip to content

Commit 35481bf

Browse files
authored
Merge pull request #336 from helxplatform/develop
Release v6.4.3
2 parents 80b0fe9 + 655f17f commit 35481bf

20 files changed

Lines changed: 298 additions & 87 deletions

File tree

.github/pull_request_template.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
## Describe your changes
2+
3+
## General Best Practice
4+
- [ ] I have performed a self-review of my code
5+
- [ ] If it is a core feature, I have added thorough tests
6+
- [ ] I have updated the documentation and README where necessary
7+
8+
## Secure Software Development
9+
- [ ] High Level Data Flow Diagrams exists for core features/functions?
10+
- [ ] Threat modeling table has been completed/updated incorporating new changes?
11+
- [ ] Code changes been validated against [OWASP Top 10?](https://owasp.org/www-project-top-ten/)
12+
- [A01:2021 - Broken Access Control](https://owasp.org/Top10/A01_2021-Broken_Access_Control/)
13+
- [A02:2021 - Cryptographic Failures](https://owasp.org/Top10/A02_2021-Cryptographic_Failures/)
14+
- [A03:2021 - Injection](https://owasp.org/Top10/A03_2021-Injection/)
15+
- [A04:2021 - Insecure Design](https://owasp.org/Top10/A04_2021-Insecure_Design/)
16+
- [A05:2021 - Security Misconfiguration](https://owasp.org/Top10/A05_2021-Security_Misconfiguration/)
17+
- [A06:2021 - Vulnerable and Outdated Components](https://owasp.org/Top10/A06_2021-Vulnerable_and_Outdated_Components/)
18+
- [A07:2021 - Identification and Authentication Failures](https://owasp.org/Top10/A07_2021-Identification_and_Authentication_Failures/)
19+
- [A08:2021 - Software and Data Integrity Failures](https://owasp.org/Top10/A08_2021-Software_and_Data_Integrity_Failures/)
20+
- [A09:2021 - Security Logging and Monitoring Failures](https://owasp.org/Top10/A09_2021-Security_Logging_and_Monitoring_Failures/)
21+
- [A10:2021 - Server-Side Request Forgery](https://owasp.org/Top10/A10_2021-Server-Side_Request_Forgery_%28SSRF%29/)

src/components/layout/layout.css

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
.helx-header {
22
padding-right: 2px !important;
3+
background: #fff;
4+
border-bottom: '1px solid #f0f0f0'
35
}
46

57
.brand_img {
68
max-height: 64px;
79
/* max-width: 150px; */
810
/* object-fit: scale-down; */
9-
padding: 2px 0px;
11+
padding: 10px 10px;
1012
}
1113

1214
.logout{
@@ -27,3 +29,22 @@
2729
.logout-button {
2830
margin: 0 1rem 0 0.5rem;
2931
}
32+
33+
34+
.helx-header.ai-sandbox-theme {
35+
background: #192d3f;
36+
border-bottom: 0;
37+
}
38+
.helx-header.ai-sandbox-theme .ant-menu-item a {
39+
color: #fff !important;
40+
}
41+
.helx-header.ai-sandbox-theme .ant-menu-item-active::after {
42+
border-color: #fff !important;
43+
}
44+
.helx-header.ai-sandbox-theme .ant-menu-item-selected::after {
45+
border-color: #fff !important;
46+
}
47+
.helx-header.ai-sandbox-theme .logout-button:disabled {
48+
color: rgba(255,255,255,0.7);
49+
border-color: rgba(255,255,255,0.7);
50+
}

src/components/layout/layout.js

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Fragment, useState } from 'react'
1+
import { Fragment, useMemo, useState } from 'react'
22
import { Layout as AntLayout, Button, Menu, Grid, Divider } from 'antd'
33
import { LinkOutlined } from '@ant-design/icons'
44
import { useLocation, useNavigate, Link } from '@gatsbyjs/reach-router'
@@ -14,7 +14,7 @@ export const Layout = ({ children }) => {
1414
const { helxAppstoreUrl, routes, context, basePath } = useEnvironment()
1515
const { api, loading: apiLoading, loggedIn, appstoreContext } = useWorkspacesAPI()
1616
const { analyticsEvents } = useAnalytics()
17-
const { md } = useBreakpoint()
17+
const { sm, md, lg } = useBreakpoint()
1818
const baseLinkPath = context.workspaces_enabled === 'true' ? '/helx' : ''
1919
const location = useLocation();
2020
const navigate = useNavigate()
@@ -42,7 +42,10 @@ export const Layout = ({ children }) => {
4242

4343
return (
4444
<AntLayout className="layout">
45-
<Header className="helx-header" style={{ display: 'flex', zIndex: 1, width: '100%', background: '#fff' }}>
45+
<Header
46+
className={`helx-header ${context.brand === 'ai_sandbox' && 'ai-sandbox-theme'}`}
47+
style={{ display: 'flex', zIndex: 1, width: '100%' }}
48+
>
4649
{context !== undefined ? <Link to={basePath}><img className="brand_img" src={'' + context.logo_url} alt={context.brand}></img></Link> : <span />}
4750
{md ? (
4851
<div style={{ flexGrow: 1, display: "flex", justifyContent: "flex-end" }}>
@@ -51,7 +54,7 @@ export const Layout = ({ children }) => {
5154
theme="light"
5255
mode="horizontal"
5356
selectedKeys={activeRoutes}
54-
style={{ display: "flex", flexGrow: 1, justifyContent: "flex-end" }}
57+
style={{ display: "flex", flexGrow: 1, justifyContent: "flex-end", background: 'transparent', border: 0 }}
5558
>
5659
<Menu.Item style={{ visibility: 'hidden' }}></Menu.Item>
5760
<Menu.Item style={{ visibility: 'hidden' }}></Menu.Item>
@@ -75,7 +78,7 @@ export const Layout = ({ children }) => {
7578
{context.workspaces_enabled === 'true' && !apiLoading && loggedIn && (
7679
<div style={{ height: "100%" }}>
7780
<Button
78-
type="primary"
81+
type={context.brand === 'ai_sandbox' ? 'default' : 'primary'}
7982
ghost
8083
className="logout-button"
8184
// Could use `loading` property but logout tends to happen so quickly that it doesn't work well.
@@ -91,6 +94,30 @@ export const Layout = ({ children }) => {
9194
<MobileMenu menu={routes} />
9295
)}
9396
</Header>
97+
{context.brand === 'ai_sandbox' && (
98+
<div style={{
99+
width: '100%',
100+
height: 64,
101+
display: 'flex',
102+
alignItems: 'center',
103+
justifyContent: 'center',
104+
backgroundImage: "url('" + context.logo_url.substring(0, context.logo_url.lastIndexOf('/')) + '/banner.png' + "')",
105+
backgroundSize: 'cover',
106+
backgroundPosition: 'center',
107+
backgroundRepeat: 'no-repeat',
108+
}}>
109+
<span style={{
110+
color: '#fff',
111+
fontSize: md ? 40 : sm ? 32 : 24,
112+
fontWeight: 300,
113+
letterSpacing: 2,
114+
textAlign: 'center',
115+
padding: '0 16px'
116+
}}>
117+
AI Applied Research Collaborations
118+
</span>
119+
</div>
120+
)}
94121
<Content className>
95122
{children}
96123
{context.workspaces_enabled === 'true' && <SidePanel />}

src/components/search/form/form.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,10 @@ export const SearchForm = ({ type=undefined, ...props }) => {
132132
doSearch(selection)
133133
}
134134

135+
const handleSearchClick = () => {
136+
doSearch(searchTerm)
137+
}
138+
135139
const searchSuggestionsWithHistory = useMemo(() => {
136140
if (searchSuggestions && searchHistory) {
137141
const suggestions = searchSuggestions.map((hit) => ({
@@ -229,7 +233,7 @@ export const SearchForm = ({ type=undefined, ...props }) => {
229233
type === MINIMAL ? (
230234
<div style={{ display: "flex", alignItems: "center", height: "100%"}}>
231235
<Divider type="vertical" style={{ height: "100%", top: 0 }} />
232-
<SearchOutlined style={{ fontSize: "16px", marginLeft: "4px" }} />
236+
<SearchOutlined style={{ fontSize: "16px", marginLeft: "4px" }} onClick={handleSearchClick} />
233237
</div>
234238
) : undefined
235239
}

src/components/workspaces/app-card.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { navigate } from '@gatsbyjs/reach-router';
44
import { RocketOutlined, InfoCircleOutlined, SettingOutlined } from '@ant-design/icons';
55
import { ExternalLink } from '../';
66
import { toBytes, bytesToMegabytes, formatBytes } from '../../utils/memory-converter';
7-
import { useActivity, useInstance, useAnalytics, useEnvironment, useWorkspacesAPI } from "../../contexts";
7+
import { useActivity, useAnalytics, useEnvironment, useWorkspacesAPI } from "../../contexts";
88
import './app-card.css';
99

1010
const { Meta } = Card;
@@ -27,7 +27,6 @@ export const AppCard = ({ name, app_id, description, detail, docs, status, minim
2727
const { context } = useEnvironment()
2828
const [launchTab, setLaunchTab] = useState(true);
2929
const [isLaunching, setLaunching] = useState(false);
30-
const { pollingInstance } = useInstance();
3130
const [currentMemory, setMemory] = useState(validateLocalstorageValue('memory', app_id, toBytes(minimum_resources.memory), toBytes(maximum_resources.memory)));
3231
const [currentCpu, setCpu] = useState(validateLocalstorageValue('cpu', app_id, minimum_resources.cpus, maximum_resources.cpus));
3332
const [currentGpu, setGpu] = useState(validateLocalstorageValue('gpu', app_id, minimum_resources.gpus, maximum_resources.gpus));
@@ -52,8 +51,6 @@ export const AppCard = ({ name, app_id, description, detail, docs, status, minim
5251
}
5352
analyticsEvents.appLaunched(name, sid, currentCpu, currentGpu, currentMemory, false)
5453
addActivity(newActivity)
55-
// start polling service and navigate to active tab if the launch is successful
56-
pollingInstance(app_id, sid, data.url, name);
5754
navigate('/helx/workspaces/active');
5855
} catch (e) {
5956
let newActivity = {

src/contexts/instance-context.js

Lines changed: 1 addition & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,9 @@
11
import React, { createContext, useContext, useState } from 'react'
2-
import axios from 'axios';
3-
import { useEnvironment } from './environment-context';
4-
import { useActivity } from './activity-context';
5-
import { useWorkspacesAPI } from './workspaces-context';
62

73
export const InstanceContext = createContext({});
84

95
export const InstanceProvider = ({ children }) => {
10-
const { helxAppstoreUrl } = useEnvironment();
11-
const { updateActivity } = useActivity();
12-
const { api } = useWorkspacesAPI();
136
const [openedTabs, setTabs] = useState([]);
14-
const [pollingTimerIDs, setPollingTimerIDs] = useState([])
15-
16-
const stopPolling = (sid) => {
17-
// stop polling when instance is deleted
18-
clearTimeout(pollingTimerIDs[sid])
19-
setPollingTimerIDs((prev) => {
20-
delete prev[sid]
21-
return prev
22-
})
23-
}
24-
25-
// Check the status code of an instance to see if it's ready for use.
26-
// Do the check every 5 seconds until it returns a OK status.
27-
// Clear its timeout when the instance is deleted by user.
28-
29-
const pollingInstance = (app_id, sid, app_url, app_name) => {
30-
const decoded_url = decodeURIComponent(app_url);
31-
const executePoll = async () => {
32-
let isAppReady = false;
33-
34-
try {
35-
isAppReady = await api.getAppReady(decoded_url);
36-
} catch(e) {} // just absorb the exception, the default false is correct here
37-
if (isAppReady) {
38-
let newActivity = {
39-
'sid': sid,
40-
'app_name': app_name,
41-
'status': 'success',
42-
'timestamp': new Date(),
43-
'message': `${app_name} is up and ready for use.`,
44-
'url': decoded_url,
45-
'app_id': app_id
46-
}
47-
updateActivity(newActivity);
48-
} else {
49-
let timerID = setTimeout(executePoll, 5000)
50-
setPollingTimerIDs((prev) => {
51-
prev[sid] = timerID
52-
return prev
53-
})
54-
}
55-
}
56-
executePoll();
57-
}
587

598
const addOrDeleteInstanceTab = (action, app_id, tabIns = undefined) => {
609
if (action === "add") {
@@ -73,7 +22,7 @@ export const InstanceProvider = ({ children }) => {
7322

7423
return (
7524
<InstanceContext.Provider value={{
76-
addOrDeleteInstanceTab, pollingInstance, stopPolling
25+
addOrDeleteInstanceTab
7726
}}>
7827
{children}
7928
</InstanceContext.Provider>

src/contexts/workspaces-context/api.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ export class WorkspacesAPI implements IWorkspacesAPI {
124124
async getActiveUser(fetchOptions: AxiosRequestConfig={}): Promise<User|null> {
125125
/** Get information about the active (logged in) user, check if whitelist required */
126126
const res = await this.axios.get<UsersResponse>("/users/", fetchOptions)
127+
// Supports old versions of appstore.
127128
if (/\/login_whitelist\/?$/.test(res.request.responseURL)) {
128129
throw new WhitelistRequiredError()
129130
}
@@ -271,7 +272,26 @@ export class WorkspacesAPI implements IWorkspacesAPI {
271272
loginSAMLUNC() {
272273
return this.loginSAML(`${this.apiUrl}../../accounts/saml/`, 448, 753)
273274
}
274-
275+
276+
@APIRequest()
277+
loginSAMLCILogon() {
278+
// return this.loginSAML(`${this.apiUrl}../../accounts/cilogon/login/?process=`, 600, 760)
279+
280+
// note: because of CILogon's cross-origin-opener-policy only redirects cant be supported
281+
const url = `${this.apiUrl}../../accounts/cilogon/login/?process=`
282+
283+
window.location.href = url
284+
}
285+
286+
@APIRequest()
287+
loginDex() {
288+
// const url = `https://helx-dex-server.apps.renci.org/dex/auth?client_id=django&response_type=code&scope=openid&redirect_uri=http://localhost:8000/accounts/dex/login/callback/`
289+
290+
const url = `${this.apiUrl}../../accounts/oidc/dex/login/`;
291+
292+
window.location.href = url
293+
}
294+
275295
@APIRequest()
276296
loginSAMLGoogle() {
277297
/** I've been informed that we aren't actually using Google SAML, we're using OAuth... but this gets the job done regardless. */

src/contexts/workspaces-context/api.types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,9 @@ export interface IWorkspacesAPI {
223223
*/
224224
loginSAMLUNC(): Promise<void>
225225
loginSAMLGoogle(): Promise<void>
226+
loginSAMLCILogon(): void
227+
loginDex(): void
228+
226229
logout(fetchOptions?: AxiosRequestConfig): Promise<LogoutResponse>
227230
/** May throw a WhitelistRequiredError */
228231
getActiveUser(fetchOptions?: AxiosRequestConfig): Promise<User|null>

0 commit comments

Comments
 (0)