🏡 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 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} />;
  } else if (!sshKeys) {
    return <Loading />;
  }

  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.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>
  );
}