Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update the project config page for improving UI/UX #765

Merged
merged 5 commits into from
Sep 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 16 additions & 23 deletions pkg/app/web/src/api/project.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,38 @@
import { apiClient, apiRequest } from "./client";
import {
DisableStaticAdminRequest,
DisableStaticAdminResponse,
EnableStaticAdminRequest,
EnableStaticAdminResponse,
GetProjectRequest,
GetProjectResponse,
UpdateProjectStaticAdminRequest,
UpdateProjectStaticAdminResponse,
UpdateProjectSSOConfigRequest,
UpdateProjectSSOConfigResponse,
UpdateProjectRBACConfigRequest,
UpdateProjectRBACConfigResponse,
EnableStaticAdminRequest,
EnableStaticAdminResponse,
DisableStaticAdminRequest,
DisableStaticAdminResponse,
UpdateProjectSSOConfigRequest,
UpdateProjectSSOConfigResponse,
UpdateProjectStaticAdminRequest,
UpdateProjectStaticAdminResponse,
} from "pipe/pkg/app/web/api_client/service_pb";
import {
ProjectSSOConfig,
ProjectRBACConfig,
ProjectSSOConfig,
} from "pipe/pkg/app/web/model/project_pb";
import { apiClient, apiRequest } from "./client";

export const getProject = (): Promise<GetProjectResponse.AsObject> => {
const req = new GetProjectRequest();
return apiRequest(req, apiClient.getProject);
};

export const updateStaticAdminPassword = ({
password,
}: {
password: string;
}): Promise<UpdateProjectStaticAdminResponse.AsObject> => {
const req = new UpdateProjectStaticAdminRequest();
req.setPassword(password);
return apiRequest(req, apiClient.updateProjectStaticAdmin);
};

export const updateStaticAdminUsername = ({
export const updateStaticAdmin = ({
username,
password,
}: {
username: string;
username?: string;
password?: string;
}): Promise<UpdateProjectStaticAdminResponse.AsObject> => {
const req = new UpdateProjectStaticAdminRequest();
req.setUsername(username);
username && req.setUsername(username);
password && req.setPassword(password);
return apiRequest(req, apiClient.updateProjectStaticAdmin);
};

Expand Down
243 changes: 162 additions & 81 deletions pkg/app/web/src/components/github-sso-form.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,48 @@
import { IconButton, makeStyles, Typography } from "@material-ui/core";
import {
Button,
CircularProgress,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
IconButton,
makeStyles,
TextField,
Typography,
} from "@material-ui/core";
import EditIcon from "@material-ui/icons/Edit";
import React, { FC, memo, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { BUTTON_TEXT_CANCEL, BUTTON_TEXT_SAVE } from "../constants/button-text";
import { SSO_DESCRIPTION } from "../constants/text";
import { UPDATE_SSO_FAILED, UPDATE_SSO_SUCCESS } from "../constants/toast-text";
import { AppState } from "../modules";
import {
fetchProject,
GitHubSSO,
Teams,
updateGitHubSSO,
updateRBAC,
} from "../modules/project";
import { fetchProject, GitHubSSO, updateGitHubSSO } from "../modules/project";
import { addToast } from "../modules/toasts";
import { AppDispatch } from "../store";
import { InputForm } from "./input-form";
import { SSOEditDialog } from "./sso-edit-dialog";
import { ProjectSettingLabeledText } from "./project-setting-labeled-text";

const useStyles = makeStyles((theme) => ({
title: {
display: "flex",
alignItems: "center",
justifyContent: "space-between",
},
description: {
paddingRight: theme.spacing(6),
},
titleWithIcon: {
display: "flex",
alignItems: "center",
},
valuesWrapper: {
padding: theme.spacing(1),
display: "flex",
justifyContent: "space-between",
},
values: {
padding: theme.spacing(2),
},
indent: {
padding: theme.spacing(1),
},
Expand All @@ -40,97 +68,150 @@ export interface GitHubSSOFormParams {
viewerTeam: string;
}

const SECTION_TITLE = "Single Sign-On";
const DIALOG_TITLE = `Edit ${SECTION_TITLE}`;

export const GithubSSOForm: FC = memo(function GithubSSOForm() {
const classes = useStyles();
const dispatch = useDispatch<AppDispatch>();
const [isEditSSO, setIsEditSSO] = useState(false);
const teams = useSelector<AppState, Teams | null>(
(state) => state.project.teams
);
const [isEdit, setIsEdit] = useState(false);
const [clientId, setClientID] = useState("");
const [clientSecret, setClientSecret] = useState("");
const [baseUrl, setBaseUrl] = useState("");
const [uploadUrl, setUploadUrl] = useState("");
const sso = useSelector<AppState, GitHubSSO | null>(
(state) => state.project.github
);

const handleSaveTeams = (params: Partial<Teams>): void => {
dispatch(updateRBAC(params)).finally(() => {
dispatch(fetchProject());
});
const handleClose = (): void => {
setIsEdit(false);
};

const handleSaveSSO = (
params: Partial<GitHubSSO> & { clientId: string; clientSecret: string }
): void => {
dispatch(updateGitHubSSO(params)).finally(() => {
dispatch(fetchProject());
const handleSave = (e: React.FormEvent<HTMLFormElement>): void => {
e.preventDefault();
dispatch(
updateGitHubSSO({ clientId, clientSecret, baseUrl, uploadUrl })
).then((result) => {
if (updateGitHubSSO.rejected.match(result)) {
dispatch(
addToast({
message: UPDATE_SSO_FAILED,
severity: "error",
})
);
} else {
dispatch(fetchProject());
dispatch(
addToast({
message: UPDATE_SSO_SUCCESS,
severity: "success",
})
);
}
});
setIsEdit(false);
};

const isInvalid = clientId === "" || clientSecret === "";

return (
<>
<Typography variant="h6">GitHub</Typography>
<div className={classes.indent}>
<Typography variant="subtitle2">Team</Typography>
<div className={classes.indent}>
<InputForm
currentValue={teams?.admin}
name="Admin Team"
onSave={(value) => handleSaveTeams({ admin: value })}
/>
<InputForm
currentValue={teams?.editor}
name="Editor Team"
onSave={(value) => handleSaveTeams({ editor: value })}
/>
<InputForm
currentValue={teams?.viewer}
name="Viewer Team"
onSave={(value) => handleSaveTeams({ viewer: value })}
/>
</div>
<div className={classes.title}>
<Typography variant="h5" className={classes.titleWithIcon}>
{SECTION_TITLE}
</Typography>
</div>

<div className={classes.indent}>
<Typography variant="subtitle2">
SSO
<IconButton onClick={() => setIsEditSSO(true)}>
<EditIcon />
</IconButton>
<div className={classes.indent}>
<div className={classes.item}>
<Typography variant="subtitle1" className={classes.name}>
Client ID
</Typography>
<Typography variant="body1">{sso?.clientId}</Typography>
</div>
<Typography
variant="body1"
color="textSecondary"
className={classes.description}
>
{SSO_DESCRIPTION}
</Typography>

<div className={classes.item}>
<Typography variant="subtitle1" className={classes.name}>
Client Secret
</Typography>
<Typography variant="body1">{sso?.clientSecret}</Typography>
</div>
<div className={classes.item}>
<Typography variant="subtitle1" className={classes.name}>
Base URL
</Typography>
<Typography variant="body1">{sso?.baseUrl}</Typography>
<div className={classes.valuesWrapper}>
{sso ? (
<>
<div className={classes.values}>
<ProjectSettingLabeledText label="Client ID" value="********" />
<ProjectSettingLabeledText
label="Client Secret"
value="********"
/>
<ProjectSettingLabeledText label="Base URL" value={sso.baseUrl} />
<ProjectSettingLabeledText
label="Upload URL"
value={sso.uploadUrl}
/>
</div>
<div className={classes.item}>
<Typography variant="subtitle1" className={classes.name}>
Upload URL
</Typography>
<Typography variant="body1">{sso?.uploadUrl}</Typography>

<div>
<IconButton onClick={() => setIsEdit(true)}>
<EditIcon />
</IconButton>
</div>
</div>
</Typography>
</>
) : (
<CircularProgress />
)}
</div>
<SSOEditDialog
currentBaseURL={sso?.baseUrl ?? ""}
currentUploadURL={sso?.uploadUrl ?? ""}
onSave={handleSaveSSO}
open={isEditSSO}
onClose={() => setIsEditSSO(false)}
/>

<Dialog
open={isEdit}
onEnter={() => {
setBaseUrl(sso?.baseUrl ?? "");
setUploadUrl(sso?.uploadUrl ?? "");
}}
onClose={handleClose}
>
<form onSubmit={handleSave}>
<DialogTitle>{DIALOG_TITLE}</DialogTitle>
<DialogContent>
<TextField
value={clientId}
variant="outlined"
margin="dense"
label="Client ID"
fullWidth
required
autoFocus
onChange={(e) => setClientID(e.currentTarget.value)}
/>
<TextField
value={clientSecret}
variant="outlined"
margin="dense"
label="Client Secret"
fullWidth
required
onChange={(e) => setClientSecret(e.currentTarget.value)}
/>
<TextField
value={baseUrl}
variant="outlined"
margin="dense"
label="Base URL"
fullWidth
onChange={(e) => setBaseUrl(e.currentTarget.value)}
/>
<TextField
value={uploadUrl}
variant="outlined"
margin="dense"
label="Upload URL"
fullWidth
onChange={(e) => setUploadUrl(e.currentTarget.value)}
/>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>{BUTTON_TEXT_CANCEL}</Button>
<Button type="submit" color="primary" disabled={isInvalid}>
{BUTTON_TEXT_SAVE}
</Button>
</DialogActions>
</form>
</Dialog>
</>
);
});
21 changes: 0 additions & 21 deletions pkg/app/web/src/components/input-form.stories.tsx

This file was deleted.

Loading