/token endpoint rendering page, wip TokenPageLayout remove Storybook context to AuthContext

This commit is contained in:
Robin K Wilson 2021-06-22 10:33:00 -07:00
Родитель 3ea3c27468
Коммит 182c2a689a
8 изменённых файлов: 246 добавлений и 14 удалений

Просмотреть файл

@ -60,6 +60,12 @@ module.exports = {
]
});
config.module.rules.push({
test: /\.(glb|gltf)$/,
use: ["file-loader"],
include: path.resolve(__dirname, "../")
});
return config;
}
};

Просмотреть файл

@ -11,7 +11,7 @@ import { faExclamationTriangle } from "@fortawesome/free-solid-svg-icons/faExcla
import { faTimes } from "@fortawesome/free-solid-svg-icons/faTimes";
import { Column } from "../layout/Column";
export function RevokeTokenModal({ onClose, revoke }) {
export function RevokeTokenModal({ onClose, onRevoke }) {
return (
<Modal
title={<FormattedMessage id="revoke-token-modal.title" defaultMessage="Revoke token" />}
@ -51,7 +51,7 @@ export function RevokeTokenModal({ onClose, revoke }) {
<Button preset="basic" sm onClick={onClose}>
<FormattedMessage id="revoke-token-modal.cancel" defaultMessage="Cancel" />
</Button>
<Button preset="accent1" sm onClick={revoke}>
<Button preset="accent1" sm onClick={onRevoke}>
<FormattedMessage id="revoke-token-modal.revoke" defaultMessage="Revoke" />
</Button>
</Row>
@ -62,5 +62,5 @@ export function RevokeTokenModal({ onClose, revoke }) {
RevokeTokenModal.propTypes = {
onClose: PropTypes.func.isRequired,
revoke: PropTypes.func.isRequired
onRevoke: PropTypes.func.isRequired
};

Просмотреть файл

@ -1,12 +1,12 @@
import React from "react";
import { CenteredModalWrapper } from "../layout/CenteredModalWrapper";
import styles from "./Tokens.scss";
import { TokenList } from "./TokenList";
import { TokenPageLayout } from "./TokenPageLayout";
import { NoAccess } from "./NoAccess";
import { RevokeTokenModal } from "./RevokeTokenModal";
import { RevealTokenModal } from "./RevealTokenModal";
import { CreateToken } from "./CreateToken";
import { StorybookAuthContextProvider } from "../auth/AuthContext";
export default {
title: "Token/Tokens"
@ -71,26 +71,35 @@ const dummyTokens = [
const noop = () => {};
// eslint-disable-next-line react/prop-types
const StorybookWrapper = ({ children }) => {
return (
<StorybookAuthContextProvider>
<TokenPageLayout>{children}</TokenPageLayout>
</StorybookAuthContextProvider>
);
};
export const NoAccessPage = () => {
return (
<TokenPageLayout className={styles.backgroundGray}>
<StorybookWrapper>
<NoAccess />
</TokenPageLayout>
</StorybookWrapper>
);
};
// eslint-disable-next-line react/prop-types
export const TokenListPage = ({ children }) => (
<TokenPageLayout>
<StorybookWrapper>
{children}
<TokenList tokens={dummyTokens} onRevokeToken={noop} />
</TokenPageLayout>
</StorybookWrapper>
);
export const EmptyTokenListPage = () => (
<TokenPageLayout>
<StorybookWrapper>
<TokenList tokens={[]} onRevokeToken={noop} />
</TokenPageLayout>
</StorybookWrapper>
);
export const RevokeTokenModalPage = () => (
@ -109,17 +118,17 @@ export function ModalRevokeToken() {
const selectedScopes = ["read_rooms", "write_rooms"];
// eslint-disable-next-line react/prop-types
export const CreateTokenPage = ({ children }) => (
<TokenPageLayout>
<StorybookWrapper>
{children}
<CreateToken scopes={scopes} scopeInfo={scopeInfo} selectedScopes={selectedScopes} />
</TokenPageLayout>
</StorybookWrapper>
);
export const ModalSaveTokenPage = () => (
<TokenPageLayout>
<StorybookWrapper>
<CenteredModalWrapper>
<RevealTokenModal onClose={noop} token={{ token: "abcd1234" }} selectedScopes={["write_rooms"]} />
</CenteredModalWrapper>
<TokenList tokens={dummyTokens} onRevokeToken={noop} />
</TokenPageLayout>
</StorybookWrapper>
);

82
src/react-components/tokens/TokensContainer.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,82 @@
import React, { useState, useEffect, useContext } from "react";
import { Token } from "./Token";
import { fetchMyTokens } from "./token-utils";
import { FormattedMessage } from "react-intl";
import styles from "./Token.scss";
import { RevokeTokenModal } from "./RevokeTokenModal";
import { RevealTokenModal } from "./RevealTokenModal";
import { TokenList } from "./TokenList";
import { NoAccess } from "./NoAccess";
import { CenteredModalWrapper } from "../layout/CenteredModalWrapper";
import { AuthContext } from "../auth/AuthContext";
export function TokensContainer() {
const [tokens, setTokens] = useState([]);
// const [showRevealTokenModal, setRevealTokenModal] = useState(false);
// const [showRevokeTokenModal, setShowRevokeTokenModal] = useState(false);
// const [selectedRevokeId, setSelectedRevokeId] = useState();
const auth = useContext(AuthContext); // Re-render when you log in/out.
useEffect(() => {
// async function updateTokens() {
// setTokens(await fetchMyTokens());
// }
// updateTokens();
}, []);
const onRevealTokenModalClose = async ({ createdNewToken }) => {
// setRevealTokenModal(false);
// if (createdNewToken) {
// setTokens(await fetchMyTokens());
// }
};
const onRevokeTokenClose = ({ removedTokenId }) => {
// if (removedTokenId) setTokens(tokens.filter(token => token.id !== removedTokenId));
// setShowRevokeTokenModal(false);
// setSelectedRevokeId("");
};
return (
<div>
{
// {showRevealTokenModal && (
// <CenteredModalWrapper>
// <RevealTokenModal onClose={onRevealTokenModalClose} />
// </CenteredModalWrapper>
// )}
// {showRevokeTokenModal && (
// <CenteredModalWrapper>
// <RevokeTokenModal selectedId={selectedRevokeId} onClose={onRevokeTokenClose} />
// </CenteredModalWrapper>
// )}
}
<button
as="a"
preset="primary"
onClick={() => {
// if (!showRevealTokenModal) setRevealTokenModal(true);
}}
>
<FormattedMessage id="tokens.create-token" defaultMessage="Create Token" />
</button>
{auth?.isAdmin ? <TokenList /> : <NoAccess />}
{tokens.map(t => {
return (
<Token
showRevokeToken={id => {
// if (!showRevokeTokenModal) {
// setSelectedRevokeId(id);
// setShowRevokeTokenModal(true);
// }
}}
key={t.id}
{...t}
/>
);
})}
</div>
);
}

65
src/react-components/tokens/token-utils.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,65 @@
import { fetchReticulumAuthenticated, getReticulumFetchUrl } from "../../utils/phoenix-utils.js";
const ENDPOINT = "/api/v2_alpha/credentials";
const CREDENTIALS_ENDPOINT_URL = getReticulumFetchUrl(ENDPOINT);
export function fetchMyTokens() {
return fetchReticulumAuthenticated(ENDPOINT).then(function(tokens) {
console.log(tokens);
return tokens.credentials;
});
}
function getHeaders() {
return {
"content-type": "application/json",
authorization: `bearer ${window.APP.store.state.credentials.token}`
};
}
export async function createToken({ scopes }) {
console.log(scopes);
const res = await fetch(CREDENTIALS_ENDPOINT_URL, {
headers: getHeaders(),
method: "POST",
body: JSON.stringify({
scopes,
subjec_type: "account"
})
});
if (res.ok) {
return res.json();
} else {
console.log(res.status);
console.log(res.statusText);
throw new Error(res.statusText);
}
// TODO handle error case
}
export async function revokeToken({ id }) {
console.log("trying revoke...");
const res = await fetch(`${CREDENTIALS_ENDPOINT_URL}/${id}?revoke`, {
headers: getHeaders(),
method: "PUT",
body: JSON.stringify({
id,
revoke: true
})
});
if (res.ok) {
return res.json();
} else {
console.log(res.status);
console.log(res.statusText);
throw new Error(res.statusText);
}
}
export function fetchAvailableScopes() {
// TODO turn into a fetch
// fetch(`${CREDENTIALS_ENDPOINT}/scopes`, {
// headers: getHeaders()
// })
return ["read_rooms", "write_rooms"];
}

28
src/tokens.html Normal file
Просмотреть файл

@ -0,0 +1,28 @@
<!DOCTYPE html>
<html>
<head>
<!-- DO NOT REMOVE/EDIT THIS COMMENT - META_TAGS -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link rel="shortcut icon" type="image/png" href="/favicon.ico">
<title>Tokens</title>
<link href="https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,400;0,500;0,700;1,400&display=swap" rel="stylesheet">
<script>
if (navigator.doNotTrack !== "1") {
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBeforea,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
}
</script>
</head>
<body>
<div id="ui-root"></div>
</body>
</html>

32
src/tokens.js Normal file
Просмотреть файл

@ -0,0 +1,32 @@
import React from "react";
import ReactDOM from "react-dom";
import { WrappedIntlProvider } from "./react-components/wrapped-intl-provider";
import registerTelemetry from "./telemetry";
import Store from "./storage/store";
import "./utils/theme";
import { AuthContextProvider } from "./react-components/auth/AuthContext";
import { TokensContainer } from "./react-components/tokens/TokensContainer";
import "./assets/stylesheets/globals.scss";
import "./react-components/styles/global.scss";
import { TokenPageLayout } from "./react-components/tokens/TokenPageLayout";
import configs from "./utils/configs";
import { ThemeProvider } from "./react-components/styles/theme";
registerTelemetry("/tokens", "Backend API Tokens Page");
const store = new Store();
window.APP = { store };
function Root() {
return (
<WrappedIntlProvider>
<ThemeProvider store={store}>
<AuthContextProvider store={store}>
<TokenPageLayout>{(true || configs.feature("public_api_access")) && <TokensContainer />}</TokenPageLayout>
</AuthContextProvider>
</ThemeProvider>
</WrappedIntlProvider>
);
}
ReactDOM.render(<Root />, document.getElementById("ui-root"));

Просмотреть файл

@ -269,6 +269,7 @@ module.exports = async (env, argv) => {
cloud: path.join(__dirname, "src", "cloud.js"),
signin: path.join(__dirname, "src", "signin.js"),
verify: path.join(__dirname, "src", "verify.js"),
tokens: path.join(__dirname, "src", "tokens.js"),
"whats-new": path.join(__dirname, "src", "whats-new.js")
},
output: {
@ -293,6 +294,7 @@ module.exports = async (env, argv) => {
{ from: /^\/discord/, to: "/discord.html" },
{ from: /^\/cloud/, to: "/cloud.html" },
{ from: /^\/verify/, to: "/verify.html" },
{ from: /^\/tokens/, to: "/tokens.html" },
{ from: /^\/whats-new/, to: "/whats-new.html" }
]
},
@ -601,6 +603,14 @@ module.exports = async (env, argv) => {
removeComments: false
}
}),
new HTMLWebpackPlugin({
filename: "tokens.html",
template: path.join(__dirname, "src", "tokens.html"),
chunks: ["tokens"],
minify: {
removeComments: false
}
}),
new CopyWebpackPlugin([
{
from: "src/hub.service.js",