52 | this.handleCredentialUpdate(
53 | platform,
54 | nodeIp,
55 | username,
56 | password,
57 | apiToken,
58 | polarisDomain
59 | )
60 | }
61 | />
62 | );
63 | } else {
64 | return (
65 |
73 | );
74 | }
75 | }
76 | }
77 |
78 | export default App;
79 |
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render( , div);
8 | ReactDOM.unmountComponentAtNode(div);
9 | });
10 |
--------------------------------------------------------------------------------
/src/components/defaultSettings/CdmApiTokenToggle.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import List from "@material-ui/core/List";
4 | import ListItem from "@material-ui/core/ListItem";
5 | import ListItemIcon from "@material-ui/core/ListItemIcon";
6 | import ListItemSecondaryAction from "@material-ui/core/ListItemSecondaryAction";
7 | import ListItemText from "@material-ui/core/ListItemText";
8 |
9 | import AssignmentIndIcon from "@material-ui/icons/AssignmentInd";
10 |
11 | import Switch from "@material-ui/core/Switch";
12 | import FormControlLabel from "@material-ui/core/FormControlLabel";
13 |
14 | const storage = window.localStorage;
15 |
16 | const convertStringToBoolean = (string) => {
17 | let bool;
18 | string === "false" ? (bool = false) : (bool = true);
19 | return bool;
20 | };
21 |
22 | const CdmApiTokenToggle = ({ handleCdmApiTokenState }) => {
23 | const [state, setState] = React.useState(
24 | storage.getItem("cdmApiToken") === null
25 | ? false
26 | : convertStringToBoolean(storage.getItem("cdmApiToken"))
27 | );
28 |
29 | const handleChange = (event) => {
30 | setState(event.target.checked);
31 | handleCdmApiTokenState(event.target.checked);
32 | };
33 |
34 | return (
35 |
36 |
37 |
38 |
39 |
40 |
44 |
45 |
54 | }
55 | />
56 |
57 |
58 |
59 | );
60 | };
61 |
62 | export default CdmApiTokenToggle;
63 |
--------------------------------------------------------------------------------
/src/components/defaultSettings/DefaultPlatformSelection.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import FormControl from "@material-ui/core/FormControl";
4 | import { makeStyles } from "@material-ui/core/styles";
5 | import List from "@material-ui/core/List";
6 | import ListItem from "@material-ui/core/ListItem";
7 | import ListItemIcon from "@material-ui/core/ListItemIcon";
8 | import ListItemSecondaryAction from "@material-ui/core/ListItemSecondaryAction";
9 | import ListItemText from "@material-ui/core/ListItemText";
10 | import Select from "@material-ui/core/Select";
11 | import MenuItem from "@material-ui/core/MenuItem";
12 |
13 | import DesktopMacIcon from "@material-ui/icons/DesktopMac";
14 |
15 | // Used to format the Select dropdown
16 | const useStyles = makeStyles(() => ({
17 | formControl: {
18 | minWidth: 100,
19 | marginRight: 20,
20 | },
21 | }));
22 |
23 | const storage = window.localStorage;
24 |
25 | const DefaultPlatform = ({ handleDefaultPlatform }) => {
26 | const classes = useStyles();
27 | const [platform, setPlatform] = React.useState(
28 | storage.getItem("platform") === null ? "none" : storage.getItem("platform")
29 | );
30 |
31 | const handleChange = (event) => {
32 | setPlatform(event.target.value);
33 | handleDefaultPlatform(event.target.value);
34 | };
35 |
36 | return (
37 |
38 |
39 |
40 |
41 |
42 |
46 |
47 |
48 |
54 | None
55 | CDM
56 | Polaris
57 |
58 |
59 |
60 |
61 |
62 | );
63 | };
64 |
65 | export default DefaultPlatform;
66 |
--------------------------------------------------------------------------------
/src/components/defaultSettings/PolarisDevelopmentModeToggle.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import List from "@material-ui/core/List";
4 | import ListItem from "@material-ui/core/ListItem";
5 | import ListItemIcon from "@material-ui/core/ListItemIcon";
6 | import ListItemSecondaryAction from "@material-ui/core/ListItemSecondaryAction";
7 | import ListItemText from "@material-ui/core/ListItemText";
8 | import CodeIcon from "@material-ui/icons/Code";
9 |
10 | import Switch from "@material-ui/core/Switch";
11 | import FormControlLabel from "@material-ui/core/FormControlLabel";
12 |
13 | const storage = window.localStorage;
14 |
15 | const convertStringToBoolean = (string) => {
16 | let bool;
17 | string === "false" ? (bool = false) : (bool = true);
18 | return bool;
19 | };
20 |
21 | const PolarisDevelopmentModeToggle = ({ handlePolarisDevModeState }) => {
22 | const [state, setState] = React.useState(
23 | storage.getItem("polarisDevMode") === null
24 | ? false
25 | : convertStringToBoolean(storage.getItem("polarisDevMode"))
26 | );
27 |
28 | const handleChange = (event) => {
29 | setState(event.target.checked);
30 |
31 | handlePolarisDevModeState(event.target.checked);
32 | };
33 |
34 | return (
35 |
36 |
37 |
38 |
39 |
40 |
44 |
45 |
53 | }
54 | />
55 |
56 |
57 |
58 | );
59 | };
60 |
61 | export default PolarisDevelopmentModeToggle;
62 |
--------------------------------------------------------------------------------
/src/components/defaultSettings/SettingsDialog.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { withStyles } from "@material-ui/core/styles";
3 |
4 | import IconButton from "@material-ui/core/IconButton";
5 | import CloseIcon from "@material-ui/icons/Close";
6 | import SettingsIcon from "@material-ui/icons/Settings";
7 |
8 | import Typography from "@material-ui/core/Typography";
9 |
10 | import Button from "@material-ui/core/Button";
11 |
12 | import Dialog from "@material-ui/core/Dialog";
13 | import MuiDialogTitle from "@material-ui/core/DialogTitle";
14 | import MuiDialogContent from "@material-ui/core/DialogContent";
15 | import MuiDialogActions from "@material-ui/core/DialogActions";
16 |
17 | import CdmApiTokenToggle from "./CdmApiTokenToggle";
18 | import PolarisDevelopmentModeToggle from "./PolarisDevelopmentModeToggle";
19 | import DefaultPlatformSelection from "./DefaultPlatformSelection";
20 |
21 | import { createMuiTheme } from "@material-ui/core/styles";
22 | import { ThemeProvider } from "@material-ui/styles";
23 |
24 | const theme = createMuiTheme({
25 | palette: {
26 | primary: {
27 | main: "#0086C0",
28 | },
29 | },
30 | });
31 |
32 | const styles = (theme) => ({
33 | root: {
34 | margin: 0,
35 | padding: theme.spacing(2),
36 | },
37 | closeButton: {
38 | position: "absolute",
39 | right: theme.spacing(1),
40 | top: theme.spacing(1),
41 | color: theme.palette.grey[500],
42 | },
43 | colorstrip: {
44 | "border-top": "solid 6px #0086C0",
45 | },
46 | });
47 |
48 | const DialogTitle = withStyles(styles)((props) => {
49 | const { children, classes, onClose, ...other } = props;
50 | return (
51 |
56 | {children}
57 | {onClose ? (
58 |
63 |
64 |
65 | ) : null}
66 |
67 | );
68 | });
69 |
70 | const DialogContent = withStyles((theme) => ({
71 | root: {
72 | padding: theme.spacing(2),
73 | },
74 | }))(MuiDialogContent);
75 |
76 | const DialogActions = withStyles((theme) => ({
77 | root: {
78 | margin: 0,
79 | padding: theme.spacing(1),
80 | },
81 | }))(MuiDialogActions);
82 |
83 | const storage = window.localStorage;
84 |
85 | export default function SettingsDialog(props) {
86 | const [settingsOpen, setSettingsOpen] = React.useState(false);
87 | const [cdmApiToken, setCdmApiTokenState] = React.useState(false);
88 | const [polarisDevMode, setPolarisDevModeState] = React.useState(false);
89 | const [defaultPlatform, setDefaultPlatform] = React.useState("none");
90 |
91 | const handleSettingsOpen = () => {
92 | setSettingsOpen(true);
93 | };
94 |
95 | const handleSettingsClose = () => {
96 | setSettingsOpen(false);
97 | };
98 |
99 | const handleSaveSettings = (event) => {
100 | event.preventDefault();
101 |
102 | storage.setItem("platform", defaultPlatform);
103 | storage.setItem("cdmApiToken", cdmApiToken);
104 | storage.setItem("polarisDevMode", polarisDevMode);
105 |
106 | props.handleDefaultUpdate(defaultPlatform, cdmApiToken, polarisDevMode);
107 | setSettingsOpen(false);
108 | };
109 |
110 | const settingsIcon = () => {
111 | return (
112 |
113 |
114 |
115 |
116 |
117 | );
118 | };
119 |
120 | // TODO: Combine the toggle handles into a single function
121 | const handleCdmApiTokenState = (state) => {
122 | setCdmApiTokenState(state);
123 | };
124 |
125 | const handlePolarisDevModeState = (state) => {
126 | setPolarisDevModeState(state);
127 | };
128 |
129 | const handleDefaultPlatform = (platform) => {
130 | setDefaultPlatform(platform);
131 | };
132 |
133 | const settingsDialog = () => {
134 | return (
135 |
136 | {settingsIcon()}
137 | handleSettingsClose(event)}
139 | aria-labelledby="customized-dialog-title"
140 | open={settingsOpen}
141 | fullWidth={true}
142 | maxWidth="md"
143 | >
144 | handleSettingsClose(event)}
147 | >
148 | Default Playground Settings
149 |
150 |
151 |
174 |
175 |
176 | );
177 | };
178 |
179 | return settingsDialog();
180 | }
181 |
--------------------------------------------------------------------------------
/src/components/graphiql/GraphiQl.css:
--------------------------------------------------------------------------------
1 | .graphiql-container .type-name {
2 | font-weight: 350;
3 | }
4 |
--------------------------------------------------------------------------------
/src/components/graphiql/GraphiQl.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import GraphiQL from "graphiql";
3 | import "graphiql/graphiql.css";
4 | // Import custom changes to the GraphiQl CSS that improves the visability
5 | // of the Types
6 | import "./GraphiQl.css";
7 | import { graphQLFetcher } from "../../utils/api";
8 | import rubrikLogo from "../../images/rubrikLogo.svg";
9 |
10 | class GraphiQl extends Component {
11 | render() {
12 | return (
13 |
15 | graphQLFetcher(
16 | graphQLParams,
17 | this.props.platform,
18 | this.props.nodeIp,
19 | this.props.username,
20 | this.props.password,
21 | this.props.apiToken,
22 | this.props.polarisDomain
23 | )
24 | }
25 | >
26 |
27 |
28 |
29 |
30 | );
31 | }
32 | }
33 |
34 | export default GraphiQl;
35 |
--------------------------------------------------------------------------------
/src/components/landingPage/LandingPage.css:
--------------------------------------------------------------------------------
1 | .landing-page {
2 | display: flex;
3 | }
4 | .welcome-message {
5 | display: flex;
6 | flex-direction: column;
7 | background-image: url("../../images/pattern.png"),
8 | radial-gradient(
9 | circle at center center,
10 | rgb(0, 88, 140) 5%,
11 | rgb(22, 41, 43) 80%
12 | );
13 | flex-grow: 1;
14 | justify-content: space-between;
15 |
16 | align-items: center;
17 | height: 100vh;
18 | }
19 |
20 | .center-white-text {
21 | color: rgb(255, 255, 255);
22 | text-align: center;
23 | font-size: 1.86rem;
24 | }
25 |
26 | .larger-font-size {
27 | font-size: 2.086rem;
28 | }
29 |
30 | .bold-text {
31 | font-weight: 600;
32 | }
33 |
34 | .bottom-white-small-text {
35 | color: rgb(255, 255, 255);
36 | font-size: 1.143rem;
37 | margin-bottom: 2.286rem;
38 | }
39 |
40 | .thin-text {
41 | font-weight: 200;
42 | }
43 |
44 | .login {
45 | -webkit-box-flex: 0;
46 | flex-grow: 0;
47 | flex-basis: 38.857rem;
48 | align-items: center;
49 | }
50 |
51 | .login-text {
52 | font-weight: 100;
53 | color: rgb(22, 41, 43);
54 | font-size: 1.986rem;
55 | margin-bottom: 1.023rem;
56 | }
57 |
58 | .error-text {
59 | font-weight: 400;
60 | color: rgb(22, 41, 43);
61 | font-size: 1.286rem;
62 | margin-bottom: 1.023rem;
63 | text-align: center;
64 | }
65 |
66 | .flex-container {
67 | height: 85%;
68 | display: -webkit-box;
69 | display: -moz-box;
70 | display: -ms-flexbox;
71 | display: -webkit-flex;
72 | display: flex;
73 | align-items: center;
74 | justify-content: center;
75 | }
76 | .flex-row {
77 | width: 85%;
78 | }
79 |
80 | .display-flex-justify {
81 | display: flex !important;
82 | justify-content: center !important;
83 | }
84 |
85 | .primary-button {
86 | border-radius: 0.286rem;
87 | line-height: 0.23;
88 | color: #fff;
89 | background-color: rgb(0, 134, 192);
90 | border-color: rgb(0, 134, 192);
91 |
92 | box-shadow: none;
93 | display: inline-block;
94 | width: 100%;
95 | margin-top: 20px;
96 | box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.2),
97 | 0 1px 5px 0 rgba(0, 0, 0, 0.12);
98 | }
99 |
100 | .primary-button:hover {
101 | background-color: #18b8e9;
102 | border-color: #18b8e9;
103 | }
104 |
105 | .btn-row {
106 | display: flex;
107 | }
108 |
109 | .btn-column {
110 | flex: 33.33%;
111 | padding: 5px;
112 | }
113 |
114 | h3 {
115 | font-weight: 200;
116 | font-size: 1.0775rem;
117 | font-family: "Open Sans", sans-serif;
118 | text-transform: uppercase;
119 | }
120 |
121 | .hide {
122 | display: none;
123 | }
124 |
125 | a:link {
126 | color: black;
127 | font-weight: 100;
128 | color: rgb(22, 41, 43);
129 | font-size: 0.986rem;
130 | margin-top: 1.023rem;
131 | text-decoration: none;
132 | }
133 |
134 | .bottom-right {
135 | position: fixed;
136 | bottom: 0;
137 | right: 0;
138 | margin-bottom: 1.023rem;
139 | margin-right: 1.023rem;
140 | }
141 |
142 | .top-right {
143 | position: fixed;
144 | top: 0;
145 | right: 0;
146 | margin-bottom: 1.023rem;
147 | margin-right: 1.023rem;
148 | }
149 |
--------------------------------------------------------------------------------
/src/components/landingPage/LandingPage.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 |
3 | import SettingsDialog from "../defaultSettings/SettingsDialog";
4 |
5 | // App Component related imports
6 | import "./LandingPage.css";
7 | import rubrikLogo from "../../images/rubrikLogo.svg";
8 | import LoginForm from "../loginForm/LoginForm";
9 | import { validateCredentials, userFiendlyErrorMessage } from "../../utils/api";
10 |
11 | // Material-UI related imports
12 | import CodeIcon from "@material-ui/icons/Code";
13 | import PersonIcon from "@material-ui/icons/Person";
14 | import IconButton from "@material-ui/core/IconButton";
15 |
16 | import Tooltip from "@material-ui/core/Tooltip";
17 | import Typography from "@material-ui/core/Typography";
18 |
19 | import { withStyles } from "@material-ui/core/styles";
20 |
21 | const selectYourPlatformFormHeader = "Select your Platform";
22 | const loginButtonText = "Login";
23 | const connectingToPlatformButtonText = "Attempting to Connect";
24 | const polarisUserDomain = ".my.rubrik.com";
25 | const polarisDevDomain = ".dev.my.rubrik-lab.com";
26 |
27 | const storage = window.localStorage;
28 |
29 | const convertStringToBoolean = (string) => {
30 | let bool;
31 | string === "false" ? (bool = false) : (bool = true);
32 | return bool;
33 | };
34 |
35 | class LandingPage extends Component {
36 | constructor(props) {
37 | super(props);
38 |
39 | this.state = {
40 | formHeader: selectYourPlatformFormHeader,
41 | platform:
42 | storage.getItem("platform") === "none"
43 | ? null
44 | : storage.getItem("platform"),
45 | loginButtonText: loginButtonText,
46 | loginErrorMessage: null,
47 | apiToken: null,
48 | ip: null,
49 | username: null,
50 | password: null,
51 | cdmApiToken: null,
52 | polarisDomain: polarisUserDomain,
53 | usingCdmApiToken:
54 | storage.getItem("cdmApiToken") === null
55 | ? false
56 | : convertStringToBoolean(storage.getItem("cdmApiToken")),
57 |
58 | settingsOpen: true,
59 | };
60 |
61 | this.createLoginForm = this.createLoginForm.bind(this);
62 | this.handleSwitchToLogin = this.handleSwitchToLogin.bind(this);
63 | this.handleSelectYourPlatform = this.handleSelectYourPlatform.bind(this);
64 | this.onFormChange = this.onFormChange.bind(this);
65 | this.handleLoginButton = this.handleLoginButton.bind(this);
66 | this.createLoginHeader = this.createLoginHeader.bind(this);
67 | this.createDevModeIcon = this.createDevModeIcon.bind(this);
68 | this.handleModeButton = this.handleModeButton.bind(this);
69 | this.handleDefaultUpdate = this.handleDefaultUpdate.bind(this);
70 | }
71 |
72 | componentDidMount() {
73 | // storage.clear();
74 |
75 | if (storage.getItem("platform") !== null) {
76 | this.setState({
77 | formHeader: loginButtonText,
78 | });
79 | }
80 |
81 | if (storage.getItem("polarisDevMode") === "true") {
82 | this.setState({
83 | polarisDomain: polarisDevDomain,
84 | });
85 | }
86 | }
87 |
88 | handleSwitchToLogin(event) {
89 | event.preventDefault();
90 |
91 | this.setState({
92 | platform: event.target.textContent.toLowerCase(),
93 | formHeader: loginButtonText,
94 | });
95 | }
96 |
97 | async handleLoginButton(event) {
98 | event.preventDefault();
99 | this.setState({
100 | loginButtonText: connectingToPlatformButtonText,
101 | loginErrorMessage: null,
102 | });
103 |
104 | try {
105 | let apiToken = await validateCredentials(
106 | this.state.platform,
107 | this.state.ip,
108 | this.state.username,
109 | this.state.password,
110 | this.state.cdmApiToken,
111 | this.state.usingCdmApiToken,
112 | this.state.polarisDomain
113 | );
114 |
115 | this.setState({
116 | apiToken: this.state.cdmApiToken ? this.state.cdmApiToken : apiToken,
117 | loginButtonText: "Login",
118 | });
119 |
120 | // If using API Token Authentication for CDM connectivity, reset the username
121 | // and password to null in case they were also filled out in the login form
122 | if (this.state.usingCdmApiToken) {
123 | this.setState({
124 | username: null,
125 | password: null,
126 | });
127 | }
128 |
129 | this.props.credentialUpdate(
130 | this.state.platform,
131 | this.state.ip,
132 | this.state.username,
133 | this.state.password,
134 | this.state.apiToken,
135 | this.state.polarisDomain
136 | );
137 | } catch (error) {
138 | this.setState({
139 | loginErrorMessage: userFiendlyErrorMessage(
140 | error,
141 | this.state.usingCdmApiToken
142 | ),
143 | loginButtonText: loginButtonText,
144 | });
145 | }
146 | }
147 |
148 | handleSelectYourPlatform(event, platform) {
149 | this.setState({
150 | platform: this.state.platform === "polaris" ? "cdm" : "polaris",
151 | loginErrorMessage: null,
152 | // usingCdmApiToken: false,
153 | });
154 | }
155 |
156 | handleDefaultUpdate(platform, cdmApiToken, polarisDevMode) {
157 | if (this.state.platform !== platform) {
158 | this.setState({
159 | platform: platform === "none" ? null : platform,
160 | });
161 | }
162 |
163 | if (this.state.usingCdmApiToken !== cdmApiToken) {
164 | this.setState({
165 | usingCdmApiToken: cdmApiToken,
166 | });
167 | }
168 |
169 | this.setState({
170 | polarisDomain:
171 | polarisDevMode === true ? polarisDevDomain : polarisUserDomain,
172 | });
173 | }
174 |
175 | handleModeButton() {
176 | this.setState({
177 | polarisDomain:
178 | this.state.polarisDomain === polarisUserDomain
179 | ? polarisDevDomain
180 | : polarisUserDomain,
181 | });
182 | }
183 |
184 | onFormChange(event) {
185 | // This function handles the input from the form fields and sets the
186 | // appropriate state.
187 |
188 | if (event.target.id !== "useApiTokenCheckBox") {
189 | this.setState({ [event.target.id]: event.target.value });
190 | } else {
191 | this.setState({
192 | usingCdmApiToken: event.target.checked,
193 | });
194 | }
195 | }
196 |
197 | createFullLandingPageUi() {
198 | return (
199 |
200 |
201 |
202 | {this.createWelcome()}
203 |
204 | {this.createLogin()}
205 |
206 |
207 | );
208 | }
209 |
210 | // Creates the "Welcome to the Rubrik GraphQL Playground" UI
211 | createWelcome() {
212 | return (
213 |
214 |
215 |
216 |
217 | Welcome to the
218 |
219 | Rubrik GraphQL Playground
220 |
221 |
222 |
223 |
224 |
225 | Don't Backup.
226 |
227 | Go Forward.
228 |
229 |
230 | );
231 | }
232 |
233 | // Creates the right side of the Welcome screen where the login form lives
234 | createLogin() {
235 | return (
236 |
237 |
238 |
239 | {this.createLoginLogo()}
240 | {this.createLoginHeader()}
241 | {this.state.platform === null && this.createLoginPlatformButtons()}
242 | {this.state.platform != null && this.createLoginForm()}
243 | {this.createRwpLink()}
244 | {this.createDevModeIcon()}
245 |
246 |
247 |
248 | );
249 | }
250 |
251 | createLoginLogo() {
252 | return (
253 |
254 |
255 |
256 | );
257 | }
258 |
259 | createLoginHeader() {
260 | return (
261 |
262 |
263 | {this.state.formHeader}
264 |
265 | {this.state.platform != null && this.createLoginErrorMessage()}
266 |
267 | );
268 | }
269 |
270 | createLoginErrorMessage() {
271 | return (
272 |
273 | {this.state.loginErrorMessage}
274 |
275 | );
276 | }
277 |
278 | createLoginPlatformButtons() {
279 | return (
280 |
281 |
282 |
287 | Polaris
288 |
289 |
290 |
291 |
296 | CDM
297 |
298 |
299 |
300 | );
301 | }
302 |
303 | createRwpLink() {
304 | // TODO: If you try to download the link when you don't have internet access the app goes blank
305 | // Until error handling is in place for this, we are are just going to return null
306 | // return (
307 | //
312 | // );
313 | return;
314 | }
315 |
316 | createDevModeIcon() {
317 | const HtmlTooltip = withStyles((theme) => ({
318 | tooltip: {
319 | backgroundColor: "rgb(255, 255, 255)",
320 | color: "rgba(0, 0, 0, 0.87)",
321 | maxWidth: 220,
322 | "text-align": "center",
323 | },
324 | }))(Tooltip);
325 |
326 | if (this.state.platform === "polaris") {
327 | return (
328 |
329 |
332 |
333 | {this.state.polarisDomain === polarisUserDomain
334 | ? "Set the Polaris Domain to development. Internal Rubrik use only."
335 | : "Set the Polaris Domain to production."}
336 |
337 |
338 | }
339 | >
340 |
341 | {this.state.polarisDomain === polarisUserDomain ? (
342 |
343 | ) : (
344 |
345 | )}
346 |
347 |
348 |
349 | );
350 | } else {
351 | return;
352 | }
353 | }
354 |
355 | createLoginForm() {
356 | return (
357 |
370 | );
371 | }
372 |
373 | render() {
374 | return this.createFullLandingPageUi();
375 | }
376 | }
377 |
378 | export default LandingPage;
379 |
--------------------------------------------------------------------------------
/src/components/loginForm/LoginForm.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import TextField from "@material-ui/core/TextField";
3 | import { makeStyles } from "@material-ui/core/styles";
4 | import { Button } from "@material-ui/core";
5 | import FormControlLabel from "@material-ui/core/FormControlLabel";
6 | import Checkbox from "@material-ui/core/Checkbox";
7 |
8 | import InputAdornment from "@material-ui/core/InputAdornment";
9 | import "../../components/landingPage/LandingPage.css";
10 |
11 | const useStylesForm = makeStyles((theme) => ({
12 | root: {
13 | "& .MuiTextField-root": {
14 | margin: theme.spacing(1),
15 | width: "100%",
16 | },
17 | },
18 | }));
19 |
20 | const useStylesSlectPlatformButton = makeStyles((theme) => ({
21 | root: {
22 | "& > *": {
23 | backgroundColor: "#FFF",
24 | "&:hover": {
25 | backgroundColor: "#FFF",
26 | },
27 | width: "100%",
28 | textTransform: "none",
29 | "font-weight": 100,
30 | "font-size": " 1.0775rem",
31 | },
32 | },
33 | }));
34 |
35 | const useStylesLoginButton = makeStyles((theme) => ({
36 | root: {
37 | "& > *": {
38 | margin: theme.spacing(1),
39 | width: "100%",
40 | "&:hover": {
41 | "background-color": "#18b8e9",
42 | "border-color": "#18b8e9",
43 | },
44 | "border-radius": "0.286rem",
45 | "line-height": " 1.83",
46 | color: "#fff",
47 | "background-color": "rgb(0, 134, 192)",
48 | "border-color": "rgb(0, 134, 192)",
49 | display: "inline-block",
50 | "margin-top": "20px",
51 | "font-weight": 200,
52 | "font-size": "1.0775rem",
53 | "text-transform": "uppercase",
54 | "box-shadow":
55 | "0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 1px 5px 0 rgba(0, 0, 0, 0.12)",
56 | },
57 | },
58 | }));
59 |
60 | export default function LoginForm(props) {
61 | const classesForm = useStylesForm();
62 | const classesSelectPlatformButton = useStylesSlectPlatformButton();
63 | const classesLoginButton = useStylesLoginButton();
64 |
65 | function diableLoginButton() {
66 | // TODO: This function controls whether or not the LOGIN button is disabled.
67 | // The logic is super super ugly and can be cleaned up but it works
68 |
69 | if (props.usingCdmApiToken === false) {
70 | if (
71 | props.ip === null ||
72 | props.username === null ||
73 | props.password === null
74 | ) {
75 | return true;
76 | }
77 | if (props.ip !== null) {
78 | if (props.ip.length <= 0) {
79 | return true;
80 | }
81 | }
82 |
83 | if (props.username !== null) {
84 | if (props.username.length <= 0) {
85 | return true;
86 | }
87 | }
88 |
89 | if (props.password !== null) {
90 | if (props.password.length <= 0) {
91 | return true;
92 | }
93 | }
94 |
95 | if (props.loginButtonText !== "Login") {
96 | return true;
97 | }
98 | } else {
99 | if (props.platform === "polaris") {
100 | if (
101 | props.ip === null ||
102 | props.username === null ||
103 | props.password === null
104 | ) {
105 | return true;
106 | }
107 | if (props.ip !== null) {
108 | if (props.ip.length <= 0) {
109 | return true;
110 | }
111 | }
112 |
113 | if (props.username !== null) {
114 | if (props.username.length <= 0) {
115 | return true;
116 | }
117 | }
118 |
119 | if (props.password !== null) {
120 | if (props.password.length <= 0) {
121 | return true;
122 | }
123 | }
124 |
125 | if (props.loginButtonText !== "Login") {
126 | return true;
127 | }
128 | } else {
129 | if (props.ip === null || props.cdmApiToken === null) {
130 | return true;
131 | }
132 | if (props.ip !== null) {
133 | if (props.ip.length <= 0) {
134 | return true;
135 | }
136 | }
137 |
138 | if (props.cdmApiToken !== null) {
139 | if (props.cdmApiToken.length <= 0) {
140 | return true;
141 | }
142 | }
143 |
144 | if (props.loginButtonText !== "Login") {
145 | return true;
146 | }
147 | }
148 | }
149 | }
150 |
151 | return (
152 |
260 | );
261 | }
262 |
--------------------------------------------------------------------------------
/src/images/pattern.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rubrikinc/graphql-playground/b7840cda3a422847538525289f71a707dc7b2d25/src/images/pattern.png
--------------------------------------------------------------------------------
/src/images/rubrikLogo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
151 |
152 |
153 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | #root {
2 | height: 100vh;
3 | }
4 |
5 | body {
6 | margin: 0;
7 | padding: 0;
8 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
9 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
10 | sans-serif;
11 | -webkit-font-smoothing: antialiased;
12 | -moz-osx-font-smoothing: grayscale;
13 | min-height: 100vh;
14 | }
15 |
16 | code {
17 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
18 | monospace;
19 | }
20 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import * as serviceWorker from './serviceWorker';
6 |
7 | ReactDOM.render( , document.getElementById('root'));
8 |
9 | // If you want your app to work offline and load faster, you can change
10 | // unregister() to register() below. Note this comes with some pitfalls.
11 | // Learn more about service workers: http://bit.ly/CRA-PWA
12 | serviceWorker.unregister();
13 |
--------------------------------------------------------------------------------
/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read http://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.1/8 is considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | export function register(config) {
24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return;
32 | }
33 |
34 | window.addEventListener('load', () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config);
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | 'This web app is being served cache-first by a service ' +
46 | 'worker. To learn more, visit http://bit.ly/CRA-PWA'
47 | );
48 | });
49 | } else {
50 | // Is not localhost. Just register service worker
51 | registerValidSW(swUrl, config);
52 | }
53 | });
54 | }
55 | }
56 |
57 | function registerValidSW(swUrl, config) {
58 | navigator.serviceWorker
59 | .register(swUrl)
60 | .then(registration => {
61 | registration.onupdatefound = () => {
62 | const installingWorker = registration.installing;
63 | if (installingWorker == null) {
64 | return;
65 | }
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === 'installed') {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the updated precached content has been fetched,
70 | // but the previous service worker will still serve the older
71 | // content until all client tabs are closed.
72 | console.log(
73 | 'New content is available and will be used when all ' +
74 | 'tabs for this page are closed. See http://bit.ly/CRA-PWA.'
75 | );
76 |
77 | // Execute callback
78 | if (config && config.onUpdate) {
79 | config.onUpdate(registration);
80 | }
81 | } else {
82 | // At this point, everything has been precached.
83 | // It's the perfect time to display a
84 | // "Content is cached for offline use." message.
85 | console.log('Content is cached for offline use.');
86 |
87 | // Execute callback
88 | if (config && config.onSuccess) {
89 | config.onSuccess(registration);
90 | }
91 | }
92 | }
93 | };
94 | };
95 | })
96 | .catch(error => {
97 | console.error('Error during service worker registration:', error);
98 | });
99 | }
100 |
101 | function checkValidServiceWorker(swUrl, config) {
102 | // Check if the service worker can be found. If it can't reload the page.
103 | fetch(swUrl)
104 | .then(response => {
105 | // Ensure service worker exists, and that we really are getting a JS file.
106 | const contentType = response.headers.get('content-type');
107 | if (
108 | response.status === 404 ||
109 | (contentType != null && contentType.indexOf('javascript') === -1)
110 | ) {
111 | // No service worker found. Probably a different app. Reload the page.
112 | navigator.serviceWorker.ready.then(registration => {
113 | registration.unregister().then(() => {
114 | window.location.reload();
115 | });
116 | });
117 | } else {
118 | // Service worker found. Proceed as normal.
119 | registerValidSW(swUrl, config);
120 | }
121 | })
122 | .catch(() => {
123 | console.log(
124 | 'No internet connection found. App is running in offline mode.'
125 | );
126 | });
127 | }
128 |
129 | export function unregister() {
130 | if ('serviceWorker' in navigator) {
131 | navigator.serviceWorker.ready.then(registration => {
132 | registration.unregister();
133 | });
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/src/utils/api.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import https from "https";
3 | import { request as httpsRequest } from "https";
4 |
5 | const defaultHttpHeader = {
6 | "Content-Type": "application/json",
7 | "User-Agent": "RubrikGraphQLPlayground",
8 | };
9 |
10 | function formatIp(ip) {
11 | /*
12 | * This function will processes the user provide CDM IP or Polaris Domain
13 | * and removes any values that will prevent connection to the platforms.
14 | */
15 |
16 | return ip
17 | .replace("https://", "")
18 | .replace(".my.rubrik.com", "")
19 | .replace("/", "");
20 | }
21 |
22 | export function userFiendlyErrorMessage(error, usingCdmApiToken) {
23 | /*
24 | * This function will processes an error mesage returned by an API call
25 | * and then provide a user friendly version of that error message
26 | */
27 | if (error.response) {
28 | if (error.response.status === 401 || error.response.status === 422) {
29 | if (usingCdmApiToken === true) {
30 | return "Sorry, you entered an incorrect API token.";
31 | } else {
32 | return "Sorry, you entered an incorrect email or password.";
33 | }
34 | } else if (error.response.status === 400 || error.response.status === 401) {
35 | return "Sorry, the request was rejected as malformed by the Rubrik platform.";
36 | } else {
37 | return `Sorry, the request was rejected with a ${error.response.status} (${error.response.data.message}) HTTP response status code.`;
38 | }
39 | } else if (
40 | error.message.includes("getaddrinfo ENOTFOUND") ||
41 | error.message.includes("Network Error")
42 | ) {
43 | return "Sorry, we were unable to connect to the provided Rubrik platform.";
44 | } else if (
45 | error.message.includes("connect ETIMEDOUT") ||
46 | error.message.includes("timeout of 15000ms exceeded")
47 | ) {
48 | return "Sorry, the connection to the Rubrik platform timeout and we were unable to connect.";
49 | } else {
50 | return error.message;
51 | }
52 | }
53 |
54 | async function rubrikApiPost(url, httpHeaders, body) {
55 | /*
56 | * This function will make an HTTP Post using the supplied endpoint and
57 | * httpHeaders.
58 | */
59 |
60 | const instance = axios.create({
61 | timeout: 15000,
62 | httpsAgent: new https.Agent({
63 | rejectUnauthorized: false,
64 | }),
65 | });
66 | const response = await instance({
67 | method: "post",
68 | url: url,
69 | headers: httpHeaders,
70 | data: body,
71 | });
72 |
73 | return response;
74 | }
75 |
76 | async function rubrikApiGet(url, httpHeaders) {
77 | /*
78 | * This function will make an HTTP Post using the supplied endpoint and
79 | * httpHeaders.
80 | */
81 |
82 | const instance = axios.create({
83 | timeout: 15000,
84 | httpsAgent: new https.Agent({
85 | rejectUnauthorized: false,
86 | }),
87 | });
88 | const response = await instance({
89 | method: "get",
90 | url: url,
91 | headers: httpHeaders,
92 | });
93 | return response;
94 | }
95 |
96 | export async function validateCredentials(
97 | platform,
98 | nodeIp,
99 | username,
100 | password,
101 | cdmApiToken,
102 | usingCdmApiToken,
103 | polarisDomain
104 | ) {
105 | /*
106 | * This function makes an API request to CDM to validate the supplied
107 | * credentials. Since we use Basic authentication to make GQL calls to CDM,
108 | * which prevent token timeouts issues, we don't actually use the returned
109 | * API Token.
110 | */
111 | let httpHeader = defaultHttpHeader;
112 | var endpoint;
113 | var postBody;
114 | if (platform === "cdm") {
115 | if (usingCdmApiToken) {
116 | httpHeader.Authorization = "Bearer " + cdmApiToken;
117 | } else {
118 | httpHeader.Authorization = "Basic " + btoa(username + ":" + password);
119 | }
120 | endpoint = "/api/v1/cluster/me/version";
121 | postBody = {};
122 | } else {
123 | httpHeader.Accept = "application/json, text/plain";
124 | httpHeader["Content-Type"] = "application/json;charset=UTF-8";
125 | endpoint = `${polarisDomain}/api/session`;
126 | postBody = {
127 | username: username,
128 | password: password,
129 | };
130 | }
131 | const formattedNodeIp = formatIp(nodeIp);
132 |
133 | let url = `https://${formattedNodeIp}${endpoint}`;
134 |
135 | let response;
136 | if (platform === "cdm") {
137 | response = await rubrikApiGet(url, httpHeader);
138 | return response.data.version;
139 | } else {
140 | response = await rubrikApiPost(url, httpHeader, postBody);
141 | return response.data.access_token;
142 | }
143 | }
144 |
145 | export function graphQLFetcher(
146 | graphQLParams,
147 | platform,
148 | nodeIp,
149 | username,
150 | password,
151 | apiToken,
152 | polarisDomain
153 | ) {
154 | /*
155 | * This function is used by GraphiQl to make the GraphQL API calls.
156 | */
157 | const formattedNodeIp = formatIp(nodeIp);
158 | let httpHeader = defaultHttpHeader;
159 | let endpointPrefix = `https://${formattedNodeIp}`;
160 | let endpointSuffixPolaris = `${polarisDomain}/api/graphql`;
161 | let endpointSuffixCdm = "/api/internal/graphql";
162 |
163 | var endpoint;
164 | if (platform === "polaris") {
165 | httpHeader.Authorization = `Bearer ${apiToken}`;
166 | endpoint = endpointPrefix + endpointSuffixPolaris;
167 | } else {
168 | if (password === null) {
169 | httpHeader.Authorization = "Bearer " + apiToken;
170 | } else {
171 | httpHeader.Authorization = "Basic " + btoa(username + ":" + password);
172 | }
173 | endpoint = endpointPrefix + endpointSuffixCdm;
174 | }
175 |
176 | // TODO: Update to use rubrikApiPost
177 | const url = new URL(endpoint);
178 |
179 | const requestOptions = {
180 | method: "post",
181 | protocol: url.protocol,
182 | hostname: url.hostname,
183 | port: url.port,
184 | path: url.pathname + url.search,
185 | headers: httpHeader,
186 | rejectUnauthorized: false, // avoid problems with self-signed certs
187 | };
188 |
189 | const request = httpsRequest(requestOptions);
190 |
191 | return new Promise((resolve, reject) => {
192 | request.on("response", (response) => {
193 | const chunks = [];
194 | response.on("data", (data) => {
195 | chunks.push(Buffer.from(data));
196 | });
197 | response.on("end", (end) => {
198 | const data = Buffer.concat(chunks).toString();
199 | if (response.statusCode >= 400) {
200 | reject(JSON.parse(data));
201 | } else {
202 | resolve(JSON.parse(data));
203 | }
204 | });
205 | });
206 |
207 | request.on("error", reject);
208 |
209 | request.end(JSON.stringify(graphQLParams));
210 | });
211 | }
212 |
--------------------------------------------------------------------------------