🏡 index : ~doyle/chartered.git

import React = require("react");
import { useState, useEffect } from "react";
import { Link } from "react-router-dom";

import Nav from "../../sections/Nav";
import { useAuth } from "../../useAuth";
import { useAuthenticatedRequest, authenticatedEndpoint } from "../../util";

import { Plus, Trash } from "react-bootstrap-icons";
import { Button, Modal, OverlayTrigger, Tooltip } from "react-bootstrap";
import HumanTime from "react-human-time";
import ErrorPage from "../ErrorPage";
import Loading, { LoadingSpinner } from "../Loading";

interface SshKeysResponse {
  keys: SshKeysResponseKey[];
}

interface SshKeysResponseKey {
  uuid: string;
  name: string;
  fingerprint: string;
  created_at: string;
  last_used_at: string;
}

export default function ListSshKeys() {
  const auth = useAuth();

  const [error, setError] = useState("");
  const [deleting, setDeleting] = useState(null);
  const [reloadSshKeys, setReloadSshKeys] = useState(0);

  const { response: sshKeys, error: loadError } =
    useAuthenticatedRequest<SshKeysResponse>(
      {
        auth,
        endpoint: "ssh-key",
      },
      [reloadSshKeys]
    );

  if (loadError) {
    return <ErrorPage message={loadError} />;
  }

  const deleteKey = async () => {
    setError("");

    try {
      let res = await fetch(
        authenticatedEndpoint(auth, `ssh-key/${deleting.uuid}`),
        {
          method: "DELETE",
          headers: {
            "Content-Type": "application/json",
          },
        }
      );
      let json = await res.json();

      if (json.error) {
        throw new Error(json.error);
      }

      setReloadSshKeys(reloadSshKeys + 1);
    } catch (e) {
      setError(e.message);
    } finally {
      setDeleting(null);
    }
  };

  const dateMonthAgo = new Date();
  dateMonthAgo.setMonth(dateMonthAgo.getMonth() - 1);

  return (
    <div className="text-white">
      <Nav />

      <div className="container mt-4 pb-4">
        <h1>Manage your SSH Keys</h1>

        <div
          className="alert alert-danger alert-dismissible"
          role="alert"
          style={{ display: error ? "block" : "none" }}
        >
          {error}

          <button
            type="button"
            className="btn-close"
            aria-label="Close"
            onClick={() => setError("")}
          ></button>
        </div>

        <div className="card border-0 shadow-sm text-black">
          {!sshKeys ? <LoadingSpinner /> : <>
            {sshKeys.keys.length == 0 ? (
              <div className="card-body">You haven't added any SSH keys yet.</div>
            ) : (
              <div className="table-responsive">
                <table className="table table-striped">
                  <tbody>
                    {sshKeys.keys.map((key) => (
                      <tr key={key.uuid}>
                        <td className="align-middle">
                          <h6 className="m-0 lh-sm">{key.name}</h6>
                          <pre className="m-0">{key.fingerprint}</pre>
                          <div className="lh-sm" style={{ fontSize: ".75rem" }}>
                            <div className="text-muted d-inline-block me-3">
                              Added{" "}
                              <OverlayTrigger
                                overlay={
                                  <Tooltip id={`${key.uuid}-created-at`}>
                                    {new Date(key.created_at).toLocaleString()}
                                  </Tooltip>
                                }
                              >
                                <span className="text-decoration-underline-dotted">
                                  <HumanTime
                                    time={new Date(key.created_at).getTime()}
                                  />
                                </span>
                              </OverlayTrigger>
                            </div>
                            <span
                              className={`text-${
                                key.last_used_at
                                  ? new Date(key.last_used_at) > dateMonthAgo
                                    ? "success"
                                    : "danger"
                                  : "muted"
                              }`}
                            >
                              Last used{" "}
                              {key.last_used_at ? (
                                <OverlayTrigger
                                  overlay={
                                    <Tooltip id={`${key.uuid}-last-used`}>
                                      {new Date(key.last_used_at).toLocaleString()}
                                    </Tooltip>
                                  }
                                >
                                  <span className="text-decoration-underline-dotted">
                                    <HumanTime
                                      time={new Date(key.last_used_at).getTime()}
                                    />
                                  </span>
                                </OverlayTrigger>
                              ) : (
                                <>never</>
                              )}
                            </span>
                          </div>
                        </td>

                        <td className="align-middle fit">
                          <button
                            type="button"
                            className="btn text-danger"
                            onClick={() => setDeleting(key)}
                          >
                            <Trash />
                          </button>
                        </td>
                      </tr>
                    ))}
                  </tbody>
                </table>
              </div>
            )}
          </>}
        </div>

        <Link
          to="/ssh-keys/add"
          className="btn btn-outline-light mt-2 float-end"
        >
          <Plus /> Add New
        </Link>
      </div>

      <DeleteModal
        show={deleting != null}
        onCancel={() => setDeleting(null)}
        onConfirm={() => deleteKey()}
        fingerprint={deleting?.fingerprint}
      />
    </div>
  );
}

function DeleteModal(props: {
  show: boolean;
  onCancel: () => void;
  onConfirm: () => void;
  fingerprint: string;
}) {
  return (
    <Modal
      show={props.show}
      onHide={props.onCancel}
      size="lg"
      aria-labelledby="delete-modal-title"
      centered
    >
      <Modal.Header closeButton>
        <Modal.Title id="delete-modal-title">
          Are you sure you wish to delete this SSH key?
        </Modal.Title>
      </Modal.Header>
      <Modal.Body>
        <p>
          Are you sure you wish to delete the SSH key with the fingerprint:{" "}
          <strong>{props.fingerprint}</strong>?
        </p>
      </Modal.Body>
      <Modal.Footer>
        <Button onClick={props.onCancel} variant="primary">
          Close
        </Button>
        <Button onClick={props.onConfirm} variant="danger">
          Delete
        </Button>
      </Modal.Footer>
    </Modal>
  );
}