From 7b16befaa8c5c7ab12441f9fb805bf84e14a2ad0 Mon Sep 17 00:00:00 2001 From: Yasser Elsayed Date: Tue, 24 Jul 2018 15:59:55 -0700 Subject: [PATCH] css fixes and removed placeholder (#1260) --- components/gcp-click-to-deploy/src/App.tsx | 65 ++-- components/gcp-click-to-deploy/src/Css.ts | 62 ++++ .../gcp-click-to-deploy/src/DeployForm.tsx | 316 +++++++++--------- components/gcp-click-to-deploy/src/Gapi.ts | 6 +- components/gcp-click-to-deploy/src/Header.tsx | 2 +- .../gcp-click-to-deploy/src/Page404.tsx | 9 +- components/gcp-click-to-deploy/src/Splash.tsx | 18 +- components/gcp-click-to-deploy/tslint.json | 2 + 8 files changed, 276 insertions(+), 204 deletions(-) create mode 100644 components/gcp-click-to-deploy/src/Css.ts diff --git a/components/gcp-click-to-deploy/src/App.tsx b/components/gcp-click-to-deploy/src/App.tsx index 172ec572c1e..f326eb0bded 100644 --- a/components/gcp-click-to-deploy/src/App.tsx +++ b/components/gcp-click-to-deploy/src/App.tsx @@ -1,5 +1,7 @@ +import { MuiThemeProvider } from '@material-ui/core'; import * as React from 'react'; import { Redirect, Route, Switch } from 'react-router-dom'; +import { CommonCss, theme } from './Css'; import DeployForm from './DeployForm'; import Gapi from './Gapi'; import Header from './Header'; @@ -16,7 +18,7 @@ declare global { const styles: { [p: string]: React.CSSProperties } = { app: { - backgroundColor: '#3b78e7', + backgroundColor: CommonCss.accent, display: 'flex', flexFlow: 'column', fontFamily: '"google sans", Helvetica', @@ -53,7 +55,7 @@ const styles: { [p: string]: React.CSSProperties } = { rightPane: { backgroundColor: '#fff', borderRadius: 5, - boxShadow: '0px 3 10px 0 #555', + boxShadow: '0 5px 15px 0 #666', margin: 'auto', position: 'relative', width: 600, @@ -91,41 +93,44 @@ class App extends React.Component { public render() { return ( -
-
-
-
-
-
- - + +
+
+
+
+
+
+ + +
+
Deploy on GCP
+
+ This deployer will guide you through the process of deploying + Kubeflow on Google Cloud Platform. It also eliminates the + need to install any tools on your machine. +
+
+
+ Specify details such as project, zone, name to create a + Kubeflow deployment. Learn more at + https://kubeflow.org +
-
Deploy on GCP
-
- This deployer will guide you through the process of deploying - Kubeflow on Google Cloud Platform. It also eliminates the - need to install any tools on your machine. +
+ + + + +
-
-
- Specify details such as project, zone, name to create a - Kubeflow deployment. Learn more at - https://kubeflow.org -
-
-
- - - - -
-
+
); } + } export default App; diff --git a/components/gcp-click-to-deploy/src/Css.ts b/components/gcp-click-to-deploy/src/Css.ts new file mode 100644 index 00000000000..fb8658b7c66 --- /dev/null +++ b/components/gcp-click-to-deploy/src/Css.ts @@ -0,0 +1,62 @@ +import { createMuiTheme } from '@material-ui/core'; + +export const CommonCss = { + accent: '#3b78e7', +}; + +export const theme = createMuiTheme({ + overrides: { + MuiButton: { + containedPrimary: { + '&:hover': { + backgroundColor: '#3d8df5', + }, + backgroundColor: '#1a73e8', + }, + root: { + color: CommonCss.accent, + fontFamily: '"google sans", Helvetica', + fontWeight: 'inherit', + textTransform: 'none', + }, + }, + MuiFormLabel: { + focused: { + color: CommonCss.accent + ' !important', + }, + root: { + color: '#333', + fontSize: 18, + fontWeight: 500, + margin: '12px 10px', + }, + }, + MuiInput: { + input: { + color: '#555', + fontSize: 15, + height: 40, + padding: '0 10px', + }, + root: { + marginTop: '25px !important', + }, + underline: { + '&:after': { + borderBottom: `2px solid ${CommonCss.accent}`, + }, + '&:before': { + borderBottom: '1px solid #ccc', + }, + '&:hover:not($disabled):not($focused):not($error):before': { + borderBottom: '1px solid #555', + }, + }, + }, + MuiInputLabel: { + root: { + color: '#555', + }, + }, + }, +}); diff --git a/components/gcp-click-to-deploy/src/DeployForm.tsx b/components/gcp-click-to-deploy/src/DeployForm.tsx index e68de892985..ad035bd920d 100644 --- a/components/gcp-click-to-deploy/src/DeployForm.tsx +++ b/components/gcp-click-to-deploy/src/DeployForm.tsx @@ -1,4 +1,3 @@ -import { createMuiTheme, MuiThemeProvider } from '@material-ui/core'; import Button from '@material-ui/core/Button'; import Dialog from '@material-ui/core/Dialog'; import DialogActions from '@material-ui/core/DialogActions'; @@ -27,102 +26,92 @@ import clusterJinjaPath from './configs/cluster.jinja'; // .endpoints..cloud.goog interface DeployFormState { - clusterJinja: string; - clusterSpec: any; deploymentName: string; - email: string; - error: string; - errorMessage: string; + dialogTitle: string; + dialogBody: string; hostName: string; ipName: string; project: string; + showLogs: boolean; zone: string; } const Text = glamorous.div({ color: '#555', - margin: '30px', + fontSize: 20, + margin: '30px 60px', }); +const logsContainerStyle = (show: boolean) => { + return { + bottom: 0, + height: show ? 120 : 0, + left: 0, + padding: 0, + position: 'fixed', + right: 0, + transition: 'height 0.3s', + } as React.CSSProperties; +}; + +const logsToggle: React.CSSProperties = { + color: '#fff', + fontWeight: 'bold', + left: 20, + position: 'absolute', + top: -40, +}; + const LogsArea = glamorous.textarea({ backgroundColor: '#333', + border: 0, boxSizing: 'border-box', - color: '#fff', - flexGrow: 1, + color: '#bbb', fontFamily: 'Source Code Pro', - fontSize: 13, - margin: '0 auto', - maxWidth: 600, - minHeight: 200, - padding: 5, + fontSize: 15, + height: '100%', + padding: 20, width: '100%', }); const Row = glamorous.div({ display: 'flex', - marginBottom: 5, - minHeight: 35, -}); - -const Label = glamorous.label({ - alignSelf: 'center', - color: '#555', - paddingLeft: 50, - textAlign: 'left', - width: 200, + marginBottom: 12, + minHeight: 56, }); const Input = glamorous(TextField)({ backgroundColor: '#f7f7f7', borderRadius: 4, color: '#666', - width: '50%', + margin: '0px 60px !important', + width: '100%', }); const DeployBtn = glamorous(Button)({ - margin: '20px !important', - width: 200, + marginRight: '20px !important', + width: 180, }); -const theme = createMuiTheme({ - overrides: { - MuiButton: { - containedPrimary: { - fontWeight: 'inherit', - }, - }, - MuiInput: { - input: { - fontSize: 15, - height: 25, - padding: 5, - }, - underline: { - '&:before': { - borderBottom: '1px solid #ccc', - }, - '&:hover:not($disabled):not($focused):not($error):before': { - borderBottom: '1px solid #555', - }, - }, - }, - }, +const YamlBtn = glamorous(Button)({ + width: 125, }); export default class DeployForm extends React.Component { + private _clusterJinja = ''; + private _clusterSpec: any; + constructor(props: any) { super(props); this.state = { - clusterJinja: '', - clusterSpec: '', deploymentName: 'kubeflow', - email: 'john@doe.com', - error: '', - errorMessage: '', + dialogBody: '', + dialogTitle: '', hostName: '.endpoints..cloud.goog', ipName: 'kubeflow', project: 'cloud-ml-dev', + showLogs: false, zone: 'us-east1-d', }; } @@ -133,19 +122,6 @@ export default class DeployForm extends React.Component { // be able to click submit until the fetches have succeeded. How can we do // that? - const email = await Gapi.getSignedInEmail(); - if (!email) { - this.setState({ - error: 'You are not signed in', - errorMessage: 'You must be signed in to use deploy Kubeflow to your GCP account.', - }); - return; - } - - this.setState({ - email, - }); - this._appendLine('loadClusterJinjaPath'); // Load the jinja template into a string because // we will need it for the deployments insert request. @@ -155,13 +131,11 @@ export default class DeployForm extends React.Component { return response.text(); }) .then((text) => { - this.setState({ - clusterJinja: text, - }); + this._clusterJinja = text; log('Loaded clusterJinja successfully'); }) .catch((error) => { - log('Request failed', error) + log('Request failed', error); }); this._appendLine('loadClusterSpec'); @@ -172,115 +146,120 @@ export default class DeployForm extends React.Component { return response.text(); }) .then((text) => { - this.setState({ - clusterSpec: jsYaml.safeLoad(text), - }); + this._clusterSpec = jsYaml.safeLoad(text); // log('Loaded clusterSpecPath successfully'); }) .catch((error) => { - log('Request failed', error) + log('Request failed', error); }); } public render() { return ( - - To get started, fill out the fields below, then click create deployment. +
+ Create a Kubeflow deployment - - - - - - + - - + - - + - - + - - + - - Create Deployment - - - Logs - - - this.setState({ error: '', errorMessage: '' })}> +
+ + Create Deployment + + + + View YAML + +
+ +
+ + +
+ + this.setState({ dialogTitle: '', dialogBody: '' })}> - {this.state.error} + {this.state.dialogTitle} - {this.state.errorMessage} + {this.state.dialogBody} - - +
); } + private _toggleLogs() { + this.setState({ + showLogs: !this.state.showLogs, + }); + } + private _appendLine(newLine: any) { const logsEl = document.querySelector('#logs') as HTMLInputElement; logsEl.value += (!!logsEl.value ? '\n' : '') + newLine; logsEl.scrollTop = logsEl.scrollHeight; } - // Create a Kubeflow deployment. - private async _createDeployment() { - for (const prop of ['project', 'zone', 'email', 'ipName', 'deploymentName', 'hostName']) { - if (this.state[prop] === '') { - this.setState({ - error: 'Missing field', - errorMessage: 'All fields are required, but it looks like you missed something.', - }); - return; - } + private async _showYaml() { + const yaml = await this._getYaml(); + this.setState({ + dialogBody: JSON.stringify(yaml) || 'Error getting YAML', + dialogTitle: yaml ? 'Deployment YAML' : 'Could not build deployment YAML', + }); + } + + private async _getYaml() { + if (!this.state.deploymentName) { + this.setState({ + dialogBody: 'All fields are required, but it looks like you missed something.', + dialogTitle: 'Missing field', + }); + return; } - const kubeflow = this.state.clusterSpec.resources[0] + const kubeflow = this._clusterSpec.resources[0]; kubeflow.name = this.state.deploymentName; kubeflow.properties.zone = this.state.zone; - // Step 0: Load the bootstrapper config. const config: any = jsYaml.safeLoad(kubeflow.properties.bootstrapperConfig); if (config == null) { this.setState({ - error: 'Deployment Error', - errorMessage: 'Property bootstrapperConfig not found in deployment config.', + dialogBody: 'Property bootstrapperConfig not found in deployment config.', + dialogTitle: 'Deployment Error', }); return; } const state = this.state; config.app.parameters.forEach((p: any) => { - if (p.name === 'acmeEmail') { - p.value = state.email; - } - if (p.name === 'ipName') { p.value = state.ipName; } @@ -290,13 +269,43 @@ export default class DeployForm extends React.Component { } }); - this.state.clusterSpec.resources[0] = kubeflow; - const yamlClusterSpec = jsYaml.dump(this.state.clusterSpec); - this._appendLine('\n----------------\nNew deployment:'); - this._appendLine('Spec:\n' + jsYaml.dump(yamlClusterSpec)); + this._clusterSpec.resources[0] = kubeflow; + const clusterSpec = jsYaml.dump(this._clusterSpec); + + return { + 'name': this.state.deploymentName, + 'target': { + 'config': { + 'content': clusterSpec, + }, + 'imports': [ + { + 'content': this._clusterJinja, + 'name': 'cluster.jinja', + } + ], + }, + }; + + } + + // Create a Kubeflow deployment. + private async _createDeployment() { + for (const prop of ['project', 'zone', 'ipName', 'deploymentName', 'hostName']) { + if (this.state[prop] === '') { + this.setState({ + dialogBody: 'All fields are required, but it looks like you missed something.', + dialogTitle: 'Missing field', + }); + return; + } + } + + this.setState({ + showLogs: true, + }); const project = this.state.project; - const deploymentName = this.state.deploymentName; // Step 1: Enable required services // Enabling takes some time, so we get the list of services that need @@ -311,13 +320,13 @@ export default class DeployForm extends React.Component { servicesToEnable = await this._getServicesToEnable(project) .catch(e => { this.setState({ - error: 'Deployment Error', - errorMessage: 'Error trying to list enabled services: ' + e, + dialogBody: 'Error trying to list enabled services: ' + e, + dialogTitle: 'Deployment Error', }); return []; }); - if (this.state.error) { + if (this.state.dialogTitle) { return; } @@ -335,12 +344,12 @@ export default class DeployForm extends React.Component { this._appendLine('Enabling ' + s); await Gapi.servicemanagement.enable(project, s) .catch(e => this.setState({ - error: 'Deployment Error', - errorMessage: 'Error trying to enable this required service: ' + s + '.\n' + e, + dialogBody: 'Error trying to enable this required service: ' + s + '.\n' + e, + dialogTitle: 'Deployment Error', })); } - if (this.state.error) { + if (this.state.dialogTitle) { return; } @@ -349,9 +358,9 @@ export default class DeployForm extends React.Component { if (servicesToEnable.length && enableAttempts >= 5) { this.setState({ - error: 'Deployment Error', - errorMessage: 'Tried too many times to enable these services: ' + + dialogBody: 'Tried too many times to enable these services: ' + servicesToEnable.join(', '), + dialogTitle: 'Deployment Error', }); return; } @@ -360,13 +369,13 @@ export default class DeployForm extends React.Component { const projectNumber = await Gapi.cloudresourcemanager.getProjectNumber(project) .catch(e => { this.setState({ - error: 'Deployment Error', - errorMessage: 'Error trying to get the project number: ' + e, + dialogBody: 'Error trying to get the project number: ' + e, + dialogTitle: 'Deployment Error', }); - return undefined + return undefined; }); - if (this.state.error) { + if (this.state.dialogTitle) { return; } @@ -374,31 +383,24 @@ export default class DeployForm extends React.Component { // Step 3: Create GCP Deployment - const resource = { - 'name': deploymentName, - 'target': { - 'config': { - 'content': yamlClusterSpec, - }, - 'imports': [ - { - 'content': this.state.clusterJinja, - 'name': 'cluster.jinja', - } - ], - }, - }; + const resource = await this._getYaml(); + if (!resource) { + return; + } + this._appendLine('Starting new deployment..'); + + const deploymentName = this.state.deploymentName; Gapi.deploymentmanager.insert(project, resource) .then(res => { this._appendLine('Result of create deployment operation:\n' + JSON.stringify(res)); - this._monitorDeployment(project, deploymentName) + this._monitorDeployment(project, deploymentName); }) .catch(err => { this._appendLine('Error trying to create deployment:\n' + err); this.setState({ - error: 'Deployment Error', - errorMessage: 'Error trying to create deployment: ' + err, + dialogBody: 'Error trying to create deployment: ' + err, + dialogTitle: 'Deployment Error', }); }); diff --git a/components/gcp-click-to-deploy/src/Gapi.ts b/components/gcp-click-to-deploy/src/Gapi.ts index d81009b325e..e00d5b9e85c 100644 --- a/components/gcp-click-to-deploy/src/Gapi.ts +++ b/components/gcp-click-to-deploy/src/Gapi.ts @@ -45,7 +45,7 @@ export default class Gapi { }); } - } + }; public static servicemanagement = class { @@ -75,7 +75,7 @@ export default class Gapi { }); } - } + }; public static cloudresourcemanager = class { @@ -89,7 +89,7 @@ export default class Gapi { }); } - } + }; public static async signIn(doPrompt?: boolean): Promise { const rePromptOptions = 'login consent select_account'; diff --git a/components/gcp-click-to-deploy/src/Header.tsx b/components/gcp-click-to-deploy/src/Header.tsx index ad95aa5c142..9438fc56440 100644 --- a/components/gcp-click-to-deploy/src/Header.tsx +++ b/components/gcp-click-to-deploy/src/Header.tsx @@ -24,7 +24,7 @@ export default class Header extends React.Component { public render() { return (
- + Google Cloud Platform diff --git a/components/gcp-click-to-deploy/src/Page404.tsx b/components/gcp-click-to-deploy/src/Page404.tsx index d76127089a9..e685fa98528 100644 --- a/components/gcp-click-to-deploy/src/Page404.tsx +++ b/components/gcp-click-to-deploy/src/Page404.tsx @@ -2,10 +2,11 @@ import * as React from 'react'; const styles = { message: { - color: '#fff', - fontSize: '1.2em', - } -} + fontSize: '1em', + margin: 20, + textAlign: 'center', + } as React.CSSProperties +}; export default class Page404 extends React.Component { public render() { diff --git a/components/gcp-click-to-deploy/src/Splash.tsx b/components/gcp-click-to-deploy/src/Splash.tsx index 681180ce690..92cd8dd0307 100644 --- a/components/gcp-click-to-deploy/src/Splash.tsx +++ b/components/gcp-click-to-deploy/src/Splash.tsx @@ -42,14 +42,14 @@ const styles: { [p: string]: React.CSSProperties } = { color: '#777', padding: 50, }, -} +}; interface SplashProps { location: { state: { from: Location; } - } + }; } // This element appears by default if the user tries to navigate to any page, @@ -76,7 +76,7 @@ export default class Spash extends React.Component; } @@ -85,21 +85,21 @@ export default class Spash extends React.Component
- Sign in to use Kubeflow + Sign in to deploy Kubeflow
- Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do - eiusmod tempor incididunt ut labore et dolore magna. + Your credentials are needed to perform GCP actions and are stored + securely clientside in the web browser (never sent to Kubeflow + servers)
- You must login to view the page at {from.pathname} - ); diff --git a/components/gcp-click-to-deploy/tslint.json b/components/gcp-click-to-deploy/tslint.json index 144c8e41ef1..dc995687fef 100644 --- a/components/gcp-click-to-deploy/tslint.json +++ b/components/gcp-click-to-deploy/tslint.json @@ -16,6 +16,8 @@ "jsx-no-lambda": false, "max-classes-per-file": false, "no-reference": false, + "quotemark": [true, "single", "jsx-double"], + "semicolon": [true, "always"], "variable-name": false } }