Skip to content

Commit

Permalink
Add new signing key
Browse files Browse the repository at this point in the history
  • Loading branch information
steverydz committed Aug 1, 2023
1 parent 0c83f82 commit 9d2e8e3
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 98 deletions.
116 changes: 37 additions & 79 deletions static/js/brand-store/components/SigningKeys/CreateSigningKeyForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@ import { useNavigate, useParams } from "react-router-dom";
import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil";
import { useMutation } from "react-query";
import { Input, Button } from "@canonical/react-components";
import randomstring from "randomstring";

import { checkModelNameExists, setPageTitle } from "../../utils";
import { checkSigningKeyExists, setPageTitle } from "../../utils";

import { brandStoresState, modelsListState, newModelState } from "../../atoms";
import { filteredModelsListState, brandStoreState } from "../../selectors";
import { signingKeysListState, newSigningKeyState } from "../../atoms";
import { filteredSigningKeysListState, brandStoreState } from "../../selectors";

import type { Store, Model } from "../../types/shared";
import type { SigningKey } from "../../types/shared";

type Props = {
setShowNotification: Function;
Expand All @@ -23,48 +22,37 @@ function CreateSigningKeyForm({
}: Props) {
const navigate = useNavigate();
const { id } = useParams();
const [newModel, setNewModel] = useRecoilState(newModelState);
const stores = useRecoilState(brandStoresState);
const currentStore = stores[0].find((store: Store) => store.id === id);
const modelsList = useRecoilValue(filteredModelsListState);
const [newSigningKey, setNewSigningKey] = useRecoilState(newSigningKeyState);
const signingKeysList = useRecoilValue(filteredSigningKeysListState);
const brandStore = useRecoilValue(brandStoreState(id));
const setModelsList = useSetRecoilState<Array<Model>>(modelsListState);
const setSigningKeysList = useSetRecoilState<Array<SigningKey>>(
signingKeysListState
);

const handleError = () => {
setShowErrorNotification(true);
setModelsList((oldModelsList: Array<Model>) => {
return oldModelsList.filter((model) => model.name !== newModel.name);
setSigningKeysList((oldSigningKeysList: Array<SigningKey>) => {
return oldSigningKeysList.filter(
(signingKey) => signingKey.name !== newSigningKey.name
);
});
navigate(`/admin/${id}/models`);
setNewModel({ name: "", apiKey: "" });
navigate(`/admin/${id}/signing-keys`);
setNewSigningKey({ name: "" });
setTimeout(() => {
setShowErrorNotification(false);
}, 5000);
};

const mutation = useMutation({
mutationFn: (newModel: { name: string; apiKey: string }) => {
mutationFn: (newSigningKey: { name: string }) => {
const formData = new FormData();

formData.set("csrf_token", window.CSRF_TOKEN);
formData.set("name", newModel.name);
formData.set("api_key", newModel.apiKey);

navigate(`/admin/${id}/models`);

setModelsList((oldModelsList: Array<Model>) => {
return [
{
"api-key": newModel.apiKey,
"created-at": new Date().toISOString(),
"modified-at": new Date().toISOString(),
name: newModel.name,
},
...oldModelsList,
];
});
formData.set("name", newSigningKey.name);

navigate(`/admin/${id}/signing-keys`);

return fetch(`/admin/store/${id}/models`, {
return fetch(`/admin/store/${id}/signing-keys`, {
method: "POST",
body: formData,
});
Expand All @@ -75,22 +63,22 @@ function CreateSigningKeyForm({
throw new Error(`${response.status} ${response.statusText}`);
}

const modelsData = await response.json();
const signingKeysData = await response.json();

if (!modelsData.success) {
throw new Error(modelsData.message);
if (!signingKeysData.success) {
throw new Error(signingKeysData.message);
}

setShowNotification(true);
setNewModel({ name: "", apiKey: "" });
navigate(`/admin/${id}/models`);
setNewSigningKey({ name: "" });
navigate(`/admin/${id}/signing-keys`);
setTimeout(() => {
setShowNotification(false);
}, 5000);
},
onError: () => {
handleError();
throw new Error("Unable to create a new model");
throw new Error("Unable to create signing key");
},
});

Expand All @@ -102,64 +90,34 @@ function CreateSigningKeyForm({
<form
onSubmit={(event) => {
event.preventDefault();
mutation.mutate({ name: newModel.name, apiKey: newModel.apiKey });
mutation.mutate({ name: newSigningKey.name });
}}
style={{ height: "100%" }}
>
<div className="p-panel is-flex-column">
<div className="p-panel__header">
<h4 className="p-panel__title">Create new model</h4>
<h4 className="p-panel__title">Create signing key</h4>
</div>
<div className="p-panel__content">
<div className="u-fixed-width">
{currentStore && (
<p>
Brand
<br />
{currentStore.name}
</p>
)}
<Input
type="text"
id="model-name-field"
id="signing-key-name-field"
placeholder="e.g. display-name-123"
label="Name"
label="Signing key name"
help="Name should contain lowercase alphanumeric characters and hyphens only"
value={newModel.name}
value={newSigningKey.name}
onChange={(e) => {
const value = e.target.value;
setNewModel({ ...newModel, name: value });
setNewSigningKey({ ...newSigningKey, name: value });
}}
error={
checkModelNameExists(newModel.name, modelsList)
? `Model ${newModel.name} already exists`
checkSigningKeyExists(newSigningKey.name, signingKeysList)
? `Model ${newSigningKey.name} already exists`
: null
}
required
/>
<Input
type="text"
id="api-key-field"
label="API key"
value={newModel.apiKey}
placeholder="yx6dnxsWQ3XUB5gza8idCuMvwmxtk1xBpa9by8TuMit5dgGnv"
className="read-only-dark"
style={{ color: "#000" }}
readOnly
/>
<Button
type="button"
onClick={() => {
setNewModel({
...newModel,
apiKey: randomstring.generate({
length: 50,
}),
});
}}
>
Generate key
</Button>
</div>
</div>
<div className="u-fixed-width">
Expand All @@ -172,7 +130,7 @@ function CreateSigningKeyForm({
className="u-no-margin--bottom"
onClick={() => {
navigate(`/admin/${id}/models`);
setNewModel({ name: "", apiKey: "" });
setNewSigningKey({ name: "" });
setShowErrorNotification(false);
}}
>
Expand All @@ -183,8 +141,8 @@ function CreateSigningKeyForm({
appearance="positive"
className="u-no-margin--bottom u-no-margin--right"
disabled={
!newModel.name ||
checkModelNameExists(newModel.name, modelsList)
!newSigningKey.name ||
checkSigningKeyExists(newSigningKey.name, signingKeysList)
}
>
Add model
Expand Down
37 changes: 26 additions & 11 deletions static/js/brand-store/components/SigningKeys/ModelsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import React from "react";
import { Modal, Icon } from "@canonical/react-components";
import { useRecoilValue } from "recoil";

import { filteredModelsListState, filteredPoliciesListState } from "../../selectors";
import {
filteredModelsListState,
filteredPoliciesListState,
} from "../../selectors";

import type { SigningKey, Model, Policy } from "../../types/shared";

Expand All @@ -17,21 +20,23 @@ function ModelsModal({
setModelsModalOpen,
signingKey,
}: Props) {

const closeHandler = () => setModelsModalOpen(false);
const filteredModels = useRecoilValue<Array<Model>>(filteredModelsListState);
const filteredPolicies = useRecoilValue<Array<Policy>>(filteredPoliciesListState);
const filteredPolicies = useRecoilValue<Array<Policy>>(
filteredPoliciesListState
);

if (!modelsModalOpen) {
return null;
}

const relatedModels = filteredModels.filter((model) =>
signingKey.models.includes(model.name)
const relatedModels = filteredModels.filter(
(model) => signingKey.models && signingKey.models.includes(model.name)
);

const relatedPolicies = filteredPolicies.filter((policy) =>
signingKey.models.includes(policy["model-name"])
const relatedPolicies = filteredPolicies.filter(
(policy) =>
signingKey.models && signingKey.models.includes(policy["model-name"])
);

return (
Expand All @@ -40,22 +45,32 @@ function ModelsModal({
<React.Fragment>
<Icon name="warning" />
{` Deactivate ${signingKey.name}`}
</React.Fragment>}
</React.Fragment>
}
close={closeHandler}
>
<h3>{signingKey.name} is used in :</h3>
<ul>
{relatedModels.map((model) => {
const relatedPolicy = relatedPolicies.find((policy) => policy["model-name"] === model.name);
const relatedPolicy = relatedPolicies.find(
(policy) => policy["model-name"] === model.name
);
return (
<React.Fragment key={model.name}>
<li>{model.name}</li>
<li>{relatedPolicy ? relatedPolicy["created-by"] : "No policy found"}</li>
<li>
{relatedPolicy
? relatedPolicy["created-by"]
: "No policy found"}
</li>
</React.Fragment>
);
})}
</ul>
<p>You need to update each policy with a new key first to be able to delete this one.</p>
<p>
You need to update each policy with a new key first to be able to delete
this one.
</p>
</Modal>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ function SigningKeysTable() {
setSelectedSigningKey(signingKey);
const modelsUsingKey = signingKeysList.some(
(key) =>
key.fingerprint === signingKey.fingerprint && key.models.length > 0
key.fingerprint === signingKey.fingerprint &&
key.models &&
key.models.length > 0
);

if (modelsUsingKey) {
Expand Down Expand Up @@ -140,7 +142,7 @@ function SigningKeysTable() {
content: signingKey["name"],
},
{
content: signingKey.policies.length,
content: signingKey.policies ? signingKey.policies.length : 0,
className: "u-align--right",
},
{
Expand All @@ -149,13 +151,14 @@ function SigningKeysTable() {
position="btm-right"
message={
<ul className="p-list u-no-margin--bottom">
{signingKey.models.map((model) => (
<li key={model}>{model}</li>
))}
{signingKey.models &&
signingKey.models.map((model) => (
<li key={model}>{model}</li>
))}
</ul>
}
>
{signingKey.models.length}
{signingKey.models ? signingKey.models.length : "0"}
</Tooltip>
),
className: "u-align--right",
Expand Down
4 changes: 2 additions & 2 deletions static/js/brand-store/types/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,6 @@ export type SigningKey = {
"modified-by": string;
fingerprint: string;
"sha3-384": string;
models: string[];
policies: string[];
models?: Array<string>;
policies?: Array<Policy>;
};
9 changes: 9 additions & 0 deletions static/js/brand-store/utils/checkSigningKeyExists.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { SigningKey } from "../types/shared";

function checkSigningKeyExists(name: string, signingKeys: Array<SigningKey>) {
return (
signingKeys.filter((signingKey) => signingKey.name === name).length > 0
);
}

export default checkSigningKeyExists;
2 changes: 2 additions & 0 deletions static/js/brand-store/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import isClosedPanel from "./isClosedPanel";
import maskString from "./maskString";
import setPageTitle from "./setPageTitle";
import getFilteredSigningKeys from "./getFilteredSigningKeys";
import checkSigningKeyExists from "./checkSigningKeyExists";

export {
checkModelNameExists,
Expand All @@ -14,4 +15,5 @@ export {
maskString,
setPageTitle,
getFilteredSigningKeys,
checkSigningKeyExists,
};

0 comments on commit 9d2e8e3

Please sign in to comment.