diff --git a/src/App.tsx b/src/App.tsx
index 1a488f612..32161da21 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -17,6 +17,7 @@ const InstanceDetail = lazy(() => import("pages/instances/InstanceDetail"));
const StorageList = lazy(() => import("pages/storage/StorageList"));
const ProfileDetail = lazy(() => import("pages/profiles/ProfileDetail"));
const OperationList = lazy(() => import("pages/operations/OperationList"));
+const CertificateAdd = lazy(() => import("pages/certificates/CertificateAdd"));
const CertificateGenerate = lazy(
() => import("pages/certificates/CertificateGenerate")
);
@@ -239,6 +240,7 @@ const App: FC = () => {
path="/ui/certificates/generate"
element={}
/>
+ } />
} />
diff --git a/src/api/certificates.tsx b/src/api/certificates.tsx
index e116f4185..cf2d0c26e 100644
--- a/src/api/certificates.tsx
+++ b/src/api/certificates.tsx
@@ -1,2 +1,19 @@
+import { handleResponse } from "util/helpers";
+
export const checkAuth = () =>
fetch("/1.0/certificates").then((response) => response.status !== 403);
+
+export const addCertificate = (token: string) => {
+ return new Promise((resolve, reject) => {
+ fetch(`/1.0/certificates`, {
+ method: "POST",
+ body: JSON.stringify({
+ type: "client",
+ password: token,
+ }),
+ })
+ .then(handleResponse)
+ .then(resolve)
+ .catch(reject);
+ });
+};
diff --git a/src/components/Navigation.tsx b/src/components/Navigation.tsx
index 6b750febe..51033bb3f 100644
--- a/src/components/Navigation.tsx
+++ b/src/components/Navigation.tsx
@@ -213,7 +213,7 @@ const Navigation: FC = () => {
{
+ const { isAuthenticated, isAuthLoading } = useAuth();
+
+ if (isAuthLoading) {
+ return ;
+ }
+
+ if (isAuthenticated) {
+ return ;
+ }
+
+ return (
+
+
+
+
Add existing certificate
+
+
+
+
+
+
+ -
+
+
+
+ Create token
+
+
+
+
+
Generate a token on the command line
+
+
+ lxc config trust add --name lxd-ui
+
+
+
+
+
+
+ -
+
+
+
+ Import
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+ Done
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default CertificateAdd;
diff --git a/src/pages/certificates/CertificateAddForm.tsx b/src/pages/certificates/CertificateAddForm.tsx
new file mode 100644
index 000000000..6c6c2a6f0
--- /dev/null
+++ b/src/pages/certificates/CertificateAddForm.tsx
@@ -0,0 +1,46 @@
+import React, { FC, useState } from "react";
+import { Button, Form, Textarea } from "@canonical/react-components";
+import { addCertificate } from "api/certificates";
+import { useNotify } from "context/notify";
+
+const CertificateAddForm: FC = () => {
+ const notify = useNotify();
+ const [token, setToken] = useState("");
+
+ const useToken = () => {
+ const sanitisedToken =
+ token
+ .trim()
+ .split(/\r?\n|\r|\n/g)
+ .at(-1) ?? "";
+
+ addCertificate(sanitisedToken)
+ .then(() => {
+ location.reload();
+ })
+ .catch((e) => notify.failure("Error using token", e));
+ };
+
+ return (
+
+ );
+};
+
+export default CertificateAddForm;
diff --git a/src/pages/certificates/CertificateMain.tsx b/src/pages/certificates/CertificateMain.tsx
index 944e74471..79dd66833 100644
--- a/src/pages/certificates/CertificateMain.tsx
+++ b/src/pages/certificates/CertificateMain.tsx
@@ -32,6 +32,13 @@ const CertificateMain: FC = () => {
>
Setup
+
+ If you have imported a client certificate to your browser previously,
+ add it to this LXD instance using tokens.
+
+
);