summaryrefslogtreecommitdiff
path: root/front/src/routes/keys.jsx
diff options
context:
space:
mode:
authorLogan Hunt <loganhunt@simponic.xyz>2023-01-19 14:04:10 -0700
committerGitHub <noreply@github.com>2023-01-19 14:04:10 -0700
commit4666d7871a9e064a3b3033c7c1daa9c3c4972d98 (patch)
tree340ee6ae1dc6410f73bb7862a89c01b7039807de /front/src/routes/keys.jsx
parentbdf99b4ee989df1813745e1dfd2983689b09ca85 (diff)
downloadchessh-4666d7871a9e064a3b3033c7c1daa9c3c4972d98.tar.gz
chessh-4666d7871a9e064a3b3033c7c1daa9c3c4972d98.zip
Web Client (#11)
* Github Oauth * A simple frontend * Add middleware proxy on dev * Forward proxy and rewrite path, add oauth to frontend, increase jwt expiry time to 12 hours * Some simple style changes * Add keys as user * Checkpoint - auth is broken * Fix auth and use player model, unrelated to this pr: flip board if dark * Close player session when password or key deleted or put * Add build script - this branch is quickly becoming cringe * Docker v2 - add migration and scripts, fix local storage and index that caused build issues * Ignore keys, proxy api correctly nginx * Finally nginx is resolved jesus christ * Remove max screen dimension limits cuz cringe * Cursor highlight * Add password form, some minor frontend changes as well * Remove cringe on home page * Move to 127.0.0.1 loopback in env * Add github id in player structs for tests
Diffstat (limited to 'front/src/routes/keys.jsx')
-rw-r--r--front/src/routes/keys.jsx204
1 files changed, 204 insertions, 0 deletions
diff --git a/front/src/routes/keys.jsx b/front/src/routes/keys.jsx
new file mode 100644
index 0000000..4dee1ce
--- /dev/null
+++ b/front/src/routes/keys.jsx
@@ -0,0 +1,204 @@
+import Modal from "react-modal";
+import { useEffect, useState, useCallback } from "react";
+import { useAuthContext } from "../context/auth_context";
+
+Modal.setAppElement("#root");
+
+const MINIMIZE_KEY_LEN = 40;
+const minimizeKey = (key) => {
+ const n = key.length;
+ if (n >= MINIMIZE_KEY_LEN) {
+ const half = Math.floor(MINIMIZE_KEY_LEN / 2);
+ return key.substring(0, half) + "..." + key.substring(n - half, n);
+ }
+ return key;
+};
+
+const KeyCard = ({ onDelete, props }) => {
+ const { id, name, key } = props;
+
+ const deleteThisKey = () => {
+ if (
+ window.confirm(
+ "Are you sure? This will close all your current ssh sessions."
+ )
+ ) {
+ fetch(`/api/keys/${id}`, {
+ credentials: "same-origin",
+ method: "DELETE",
+ })
+ .then((r) => r.json())
+ .then((d) => d.success && onDelete && onDelete());
+ }
+ };
+
+ return (
+ <div className="key-card">
+ <h4 style={{ flex: 1 }}>{name}</h4>
+ <p style={{ flex: 4 }}>{minimizeKey(key)}</p>
+
+ <button
+ style={{ flex: 0 }}
+ className="button red"
+ onClick={deleteThisKey}
+ >
+ Delete
+ </button>
+ </div>
+ );
+};
+
+const AddKeyButton = ({ onSave }) => {
+ const [open, setOpen] = useState(false);
+ const [name, setName] = useState("");
+ const [key, setKey] = useState("");
+ const [errors, setErrors] = useState(null);
+
+ const setDefaults = () => {
+ setName("");
+ setKey("");
+ setErrors(null);
+ };
+
+ const close = () => {
+ setDefaults();
+ setOpen(false);
+ };
+
+ const createKey = () => {
+ fetch(`/api/player/keys`, {
+ credentials: "same-origin",
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ key: key.trim(),
+ name: name.trim(),
+ }),
+ })
+ .then((r) => r.json())
+ .then((d) => {
+ if (d.success) {
+ if (onSave) {
+ onSave();
+ }
+ close();
+ } else if (d.errors) {
+ if (typeof d.errors === "object") {
+ setErrors(
+ Object.keys(d.errors).map(
+ (field) => `${field}: ${d.errors[field].join(",")}`
+ )
+ );
+ } else {
+ setErrors([d.errors]);
+ }
+ }
+ });
+ };
+
+ return (
+ <div>
+ <button className="button" onClick={() => setOpen(true)}>
+ + Add Key
+ </button>
+ <Modal
+ isOpen={open}
+ onRequestClose={close}
+ className="modal"
+ contentLabel="Add Key"
+ >
+ <div>
+ <h3>Add SSH Key</h3>
+ <p>
+ Not sure about this? Check{" "}
+ <a
+ href="https://www.ssh.com/academy/ssh/keygen"
+ target="_blank"
+ rel="noreferrer"
+ >
+ here
+ </a>{" "}
+ for help!
+ </p>
+ <hr />
+ <p>Key Name *</p>
+ <input
+ value={name}
+ onChange={(e) => setName(e.target.value)}
+ required
+ />
+ </div>
+ <div>
+ <p>SSH Key *</p>
+ <textarea
+ cols={40}
+ rows={5}
+ value={key}
+ onChange={(e) => setKey(e.target.value)}
+ required
+ />
+ </div>
+ <div>
+ {errors && (
+ <div style={{ color: "red" }}>
+ {errors.map((error, i) => (
+ <p key={i}>{error}</p>
+ ))}
+ </div>
+ )}
+ </div>
+ <div className="flex-end-row">
+ <button className="button" onClick={createKey}>
+ Add
+ </button>
+ <button className="button red" onClick={close}>
+ Cancel
+ </button>
+ </div>
+ </Modal>
+ </div>
+ );
+};
+
+export const Keys = () => {
+ const {
+ player: { id: userId },
+ } = useAuthContext();
+ const [keys, setKeys] = useState(null);
+
+ const refreshKeys = useCallback(
+ () =>
+ fetch(`/api/player/${userId}/keys`)
+ .then((r) => r.json())
+ .then((keys) => setKeys(keys)),
+ [userId]
+ );
+
+ useEffect(() => {
+ if (userId) {
+ refreshKeys();
+ }
+ }, [userId, refreshKeys]);
+
+ if (!keys) return <p>Loading...</p>;
+
+ if (Array.isArray(keys)) {
+ return (
+ <>
+ <h2>My Keys</h2>
+ <AddKeyButton onSave={refreshKeys} />
+ <div className="key-card-collection">
+ {keys.length ? (
+ keys.map((key) => (
+ <KeyCard key={key.id} onDelete={refreshKeys} props={key} />
+ ))
+ ) : (
+ <p>Looks like you've got no keys, try adding some!</p>
+ )}
+ </div>
+ </>
+ );
+ }
+};