Clean up ui

This commit is contained in:
René Pfeuffer
2020-10-02 16:38:50 +02:00
parent 2c582c4fde
commit bf0a477ab7
6 changed files with 108 additions and 28 deletions

View File

@@ -83,10 +83,13 @@
"addKey": "Schlüssel hinzufügen",
"delete": "Löschen",
"download": "Herunterladen",
"text1": "Erstelle und verwalte Personal Access Token um auf die REST API zuzugreifen oder diese als Passwort für SCM-Clients zu nutzen. Die Rechte der Token sind auf Repositories und die gewählte Rolle beschränkt. Sie können die Rollenberechtigungen in der Administration unter „Berechtigungsrollen“ einsehen und neue Rollen anlegen.",
"text2": "Um den Token in REST-Abfragen zu nutzen, übergeben Sie diesen als Cookie mit dem Namen „X-Bearer-Token“. Sie können den Token auch anstelle Ihres Passworts nutzen, um sich mit SCM-Clients anzumelden.",
"modal": {
"title": "Schlüssel erzeugt",
"text1": "Hier ist der neue Schlüssel. Er kann als \"Bearer Token\" für REST Zugriffe oder als Passwort für SCM Clients genutzt werden. Sie erhalten Ihre Zugriffsrechte, eingeschränkt auf Repositories und die gewählte Rolle.",
"text2": "Dieses ist der einzige Zeitpunkt, an dem der Schlüssel angezeigt wird. Er kann im Nachhinein nicht mehr hergestellt werden. Bewahren Sie ihn an einem sicheren Ort auf.",
"text1": "Ihr neuer API-Schlüssel ist bereit. Sie können diesen als Token für Zugriffe auf die REST-Schnittstelle nutzen oder anstelle Ihres Passworts zum Login mit SCM-Clients nutzen.",
"text2": "Sichern Sie Ihren API-Schlüssel jetzt! Er wird hier einmalig angezeigt und kann später nicht mehr wiederbeschafft werden.",
"clipboard": "In die Zwischenablage kopieren",
"close": "Schließen"
}
}

View File

@@ -83,10 +83,13 @@
"addKey": "Add key",
"delete": "Delete",
"download": "Download",
"text1": "Create and manage personal access tokens to access the REST API or use as a password for SCM clients. The privileges of these tokens are limited to repositories and the selected role. You may view and create roles in the administration view “Permission Roles”.",
"text2": "To use the token in a REST request, pass it as a cookie named “X-Bearer-Token”. You may use the token as your password for SCM clients, too.",
"modal": {
"title": "Key created",
"text1": "Here is your new key. You can use it as a bearer token for rest calls or as a password for scm clients. Doing so you will have your permissions limited to repositories and the selected role.",
"text2": "This is the only time it will be shown, it cannot be recovered afterwards. Keep it in a safe place.",
"text1": "Your new API key is ready. You can use it as a bearer token for REST calls or as a password for SCM clients.",
"text2": "Store your API key in a safe place now! It is only displayed now and cannot be recovered later.",
"clipboard": "Copy to clipboard",
"close": "Close"
}
}

View File

@@ -34,7 +34,7 @@ import {
import { RepositoryRole } from "@scm-manager/ui-types";
import { getRepositoryRolesLink, getRepositoryVerbsLink } from "../../../modules/indexResource";
import RoleSelector from "../../../repos/permissions/components/RoleSelector";
import { Button, Modal } from "@scm-manager/ui-components";
import ApiKeyCreatedModal from "./ApiKeyCreatedModal";
type Props = {
createLink: string;
@@ -58,7 +58,7 @@ const AddApiKey: FC<Props> = ({
const [error, setError] = useState<undefined | Error>();
const [displayName, setDisplayName] = useState("");
const [permissionRole, setPermissionRole] = useState("");
const [addedPassphrase, setAddedPassphrase] = useState("");
const [addedKey, setAddedKey] = useState("");
useEffect(() => {
if (!availableRepositoryRoles) {
@@ -80,7 +80,7 @@ const AddApiKey: FC<Props> = ({
apiClient
.post(createLink, { displayName: displayName, permissionRole: permissionRole }, CONTENT_TYPE_API_KEY)
.then(response => response.text())
.then(setAddedPassphrase)
.then(setAddedKey)
.then(() => setLoading(false))
.catch(setError);
};
@@ -98,30 +98,14 @@ const AddApiKey: FC<Props> = ({
const closeModal = () => {
resetForm();
refresh();
setAddedPassphrase("");
setAddedKey("");
};
const newPassphraseModalContent = (
<div className={"media-content"}>
<p>{t("apiKey.modal.text1")}</p>
<p><b>{t("apiKey.modal.text2")}</b></p>
<pre>{addedPassphrase}</pre>
</div>
);
const newPassphraseModal = addedPassphrase && (
<Modal
body={newPassphraseModalContent}
closeFunction={closeModal}
title={t("apiKey.modal.title")}
footer={<Button label={t("apiKey.modal.close")} action={closeModal} />}
active={true}
/>
);
const newKeyModal = addedKey && <ApiKeyCreatedModal addedKey={addedKey} close={closeModal} />;
return (
<>
{newPassphraseModal}
{newKeyModal}
<InputField label={t("apiKey.displayName")} value={displayName} onChange={setDisplayName} />
<RoleSelector
loading={!availableRoleNames}

View File

@@ -0,0 +1,81 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import React, { FC, useRef, useState } from "react";
import { Button, Icon, Modal } from "@scm-manager/ui-components";
import { useTranslation } from "react-i18next";
import styled from "styled-components";
type Props = {
addedKey: string;
close: () => void;
};
const KeyArea = styled.textarea`
white-space: nowrap;
overflow: auto;
font-family: "Courier New", Monaco, Menlo, "Ubuntu Mono", "source-code-pro", monospace;
`;
const ApiKeyCreatedModal: FC<Props> = ({ addedKey, close }) => {
const [t] = useTranslation("users");
const [copied, setCopied] = useState(false);
const keyRef = useRef(null);
const copy = () => {
keyRef.current.select();
document.execCommand("copy");
setCopied(true);
};
const newPassphraseModalContent = (
<div className={"media-content"}>
<p>{t("apiKey.modal.text1")}</p>
<p>
<b>{t("apiKey.modal.text2")}</b>
</p>
<hr />
<div className={"columns"}>
<div className={"column is-11"}>
<KeyArea wrap={"soft"} ref={keyRef} className={"input"} value={addedKey} />
</div>
<div className={"column is-1"}>
<Icon className={"is-hidden-mobile fa-2x"} name={copied ? "clipboard-check" : "clipboard"} title={t("apiKey.modal.clipboard")} onClick={copy} />
</div>
</div>
</div>
);
return (
<Modal
body={newPassphraseModalContent}
closeFunction={close}
title={t("apiKey.modal.title")}
footer={<Button label={t("apiKey.modal.close")} action={close} />}
active={true}
/>
);
};
export default ApiKeyCreatedModal;

View File

@@ -23,9 +23,10 @@
*/
import React, { FC } from "react";
import { DateFromNow } from "@scm-manager/ui-components";
import { DateFromNow, Icon } from "@scm-manager/ui-components";
import { ApiKey } from "./SetApiKeys";
import { Link } from "@scm-manager/ui-types";
import { useTranslation } from "react-i18next";
type Props = {
apiKey: ApiKey;
@@ -33,12 +34,13 @@ type Props = {
};
export const ApiKeyEntry: FC<Props> = ({ apiKey, onDelete }) => {
const [t] = useTranslation("users");
let deleteButton;
if (apiKey?._links?.delete) {
deleteButton = (
<a className="level-item" onClick={() => onDelete((apiKey._links.delete as Link).href)}>
<span className="icon is-small">
<i className="fas fa-trash" />
<Icon name="trash" className="fas" title={t("apiKey.delete")} />
</span>
</a>
);

View File

@@ -27,6 +27,7 @@ import React, { FC, useEffect, useState } from "react";
import { apiClient, ErrorNotification, Loading } from "@scm-manager/ui-components";
import ApiKeyTable from "./ApiKeyTable";
import AddApiKey from "./AddApiKey";
import { useTranslation } from "react-i18next";
export type ApiKeysCollection = Collection & {
_embedded: {
@@ -49,6 +50,7 @@ type Props = {
};
const SetApiKeys: FC<Props> = ({ user }) => {
const [t] = useTranslation("users");
const [error, setError] = useState<undefined | Error>();
const [loading, setLoading] = useState(false);
const [apiKeys, setApiKeys] = useState<ApiKeysCollection | undefined>(undefined);
@@ -86,6 +88,11 @@ const SetApiKeys: FC<Props> = ({ user }) => {
return (
<>
<div className={"media-content"}>
<p>{t("apiKey.text1")}</p>
<p>{t("apiKey.text2")}</p>
</div>
<hr />
<ApiKeyTable apiKeys={apiKeys} onDelete={onDelete} />
{createLink && <AddApiKey createLink={createLink} refresh={fetchApiKeys} />}
</>