From a087961fab0d73cc54f9cae658e9eb50ab060f96 Mon Sep 17 00:00:00 2001 From: Jon Herron Date: Thu, 13 Oct 2022 20:45:09 -0400 Subject: [PATCH] Squashed 'spiffworkflow-frontend/' changes from 572d779f7..d68d521d7 d68d521d7 also alert for unexpected errors from the api result w/ burnettk 698f7595a show an alert if backend gives a 403 w/ burnettk fa65efbb3 Merge branch 'main' of github.com:sartography/spiffworkflow-frontend f2a629110 do not null out error message in useEffect to avoid endless redirects w/ burnettk b5b79bae7 Remove the AllowedModel stuff from secrets Clean up spacing on SecretNew Don't allow spaces in Secret key. Change them to dashes. 9ab1da07f Merge branch 'main' into feature/secrets 4936296bd add link for path column too 4272ad5ac No slugify, too extreme for this use case d5cdfed18 Merge remote-tracking branch 'origin/main' into feature/secrets 4a81b2a10 add SecretAllowedModelEdit 87f640e3a Don't allow spaces on secret keys c627ef9e8 Don't display value on SecretList d42bd4ced fix method shorthand 82340ddc2 commit for KB a2c210a5a First stab at edit 1a2646249 fix typo - allowed_process_models -> allowed_processes 519555919 add interface for allowed SecretAllowedProcessModel, and use it in Secret interface use Secret and SecretAllowedProcessModel interfaces instead of `any` in SecretShow 6ccc7dd31 Merge branch 'main' into feature/secrets accd8e364 Moved button to bottom Added Secrets heading 18b80556a delete allowed model path 2dfe4c41a remove unused code df006b2af Rename Allowed Process Path to Allowed Model 8a68de12a add SecretAllowedModelNew eadf54357 Merge branch 'main' into feature/secrets f016b90b8 Cleaning up delete 7e5db93f0 Committing so the machine can look at an error 8655e437d Delete 2 ways -- icon and button w/icon ce537d7e8 display list of allowed processes skeleton of delete using MdDelete icon ab19657c0 install react-icons git-subtree-dir: spiffworkflow-frontend git-subtree-split: d68d521d78f747d156cfd45e6a7b26f4a5f5d63b --- package-lock.json | 20 ++++++++++++-- package.json | 1 + src/interfaces.ts | 3 +- src/routes/SecretList.tsx | 34 ++++++++++++++++++----- src/routes/SecretNew.tsx | 34 +++++++++++++++++++---- src/routes/SecretShow.tsx | 55 +++++++++++++++++++++++++++++++------ src/routes/TaskShow.tsx | 1 - src/services/HttpService.ts | 10 +++++++ 8 files changed, 133 insertions(+), 25 deletions(-) diff --git a/package-lock.json b/package-lock.json index 765c27902..f6b8687d9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,6 +43,7 @@ "react-bootstrap-typeahead": "^6.0.0", "react-datepicker": "^4.8.0", "react-dom": "^18.2.0", + "react-icons": "^4.4.0", "react-jsonschema-form": "^1.8.1", "react-markdown": "^8.0.3", "react-router-dom": "^6.3.0", @@ -22404,6 +22405,14 @@ "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz", "integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==" }, + "node_modules/react-icons": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.4.0.tgz", + "integrity": "sha512-fSbvHeVYo/B5/L4VhB7sBA1i2tS8MkT0Hb9t2H1AVPkwGfVHLJCqyr2Py9dKMxsyM63Eng1GkdZfbWj+Fmv8Rg==", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "16.9.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.9.0.tgz", @@ -44975,7 +44984,7 @@ "@csstools/postcss-text-decoration-shorthand": "^1.0.0", "@csstools/postcss-trigonometric-functions": "^1.0.2", "@csstools/postcss-unset-value": "^1.0.2", - "autoprefixer": "10.4.8", + "autoprefixer": "10.4.5", "browserslist": "^4.21.3", "css-blank-pseudo": "^3.0.3", "css-has-pseudo": "^3.0.4", @@ -45013,7 +45022,8 @@ }, "dependencies": { "autoprefixer": { - "version": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.5.tgz", + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.5.tgz", "integrity": "sha512-Fvd8yCoA7lNX/OUllvS+aS1I7WRBclGXsepbvT8ZaPgrH24rgXpZzF0/6Hh3ZEkwg+0AES/Osd196VZmYoEFtw==", "requires": { "browserslist": "^4.20.2", @@ -45721,6 +45731,12 @@ "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz", "integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==" }, + "react-icons": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.4.0.tgz", + "integrity": "sha512-fSbvHeVYo/B5/L4VhB7sBA1i2tS8MkT0Hb9t2H1AVPkwGfVHLJCqyr2Py9dKMxsyM63Eng1GkdZfbWj+Fmv8Rg==", + "requires": {} + }, "react-is": { "version": "16.9.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.9.0.tgz", diff --git a/package.json b/package.json index 4394c25fc..2ce2d7a13 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "react-bootstrap-typeahead": "^6.0.0", "react-datepicker": "^4.8.0", "react-dom": "^18.2.0", + "react-icons": "^4.4.0", "react-jsonschema-form": "^1.8.1", "react-markdown": "^8.0.3", "react-router-dom": "^6.3.0", diff --git a/src/interfaces.ts b/src/interfaces.ts index 2e023c9a2..a628940ba 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -1,7 +1,8 @@ export interface Secret { + id: number; key: string; value: string; - username: string; + creator_user_id: string; } export interface RecentProcessModel { diff --git a/src/routes/SecretList.tsx b/src/routes/SecretList.tsx index b7a85b190..2b9e756ad 100644 --- a/src/routes/SecretList.tsx +++ b/src/routes/SecretList.tsx @@ -1,6 +1,7 @@ import { useEffect, useState } from 'react'; import { Link, useSearchParams } from 'react-router-dom'; import { Button, Table } from 'react-bootstrap'; +import { MdDelete } from 'react-icons/md'; import PaginationForTable from '../components/PaginationForTable'; import HttpService from '../services/HttpService'; import { getPageInfoFromSearchParams } from '../helpers'; @@ -23,17 +24,36 @@ export default function SecretList() { }); }, [searchParams]); + const reloadSecrets = (_result: any) => { + window.location.reload(); + }; + + const handleDeleteSecret = (key: any) => { + HttpService.makeCallToBackend({ + path: `/secrets/${key}`, + successCallback: reloadSecrets, + httpMethod: 'DELETE', + }); + }; + const buildTable = () => { const rows = secrets.map((row) => { return ( + + + {(row as any).id} + + {(row as any).key} - {(row as any).value} {(row as any).username} + + handleDeleteSecret((row as any).key)} /> + ); }); @@ -41,9 +61,10 @@ export default function SecretList() { + - + {rows} @@ -72,12 +93,11 @@ export default function SecretList() { if (pagination) { return ( - <> - -
-
+
+

Secrets

{SecretsDisplayArea()} - + +
); } return null; diff --git a/src/routes/SecretNew.tsx b/src/routes/SecretNew.tsx index 01c8de5bf..7a697a98a 100644 --- a/src/routes/SecretNew.tsx +++ b/src/routes/SecretNew.tsx @@ -1,5 +1,6 @@ import { useState } from 'react'; import { useNavigate } from 'react-router-dom'; +import { Stack } from 'react-bootstrap'; import Button from 'react-bootstrap/Button'; import Form from 'react-bootstrap/Form'; import HttpService from '../services/HttpService'; @@ -13,6 +14,18 @@ export default function SecretNew() { navigate(`/admin/secrets/${key}`); }; + const navigateToSecrets = () => { + navigate(`/admin/secrets`); + }; + + const changeSpacesToDash = (someString: string) => { + // change spaces to `-` + let s1 = someString.replace(' ', '-'); + // remove any trailing `-` + s1 = s1.replace(/-$/, ''); + return s1; + }; + const addSecret = (event: any) => { event.preventDefault(); HttpService.makeCallToBackend({ @@ -26,16 +39,22 @@ export default function SecretNew() { }); }; + const warningStyle = { + color: 'red', + }; + return (

Add Secret

- Key: + + Key: No Spaces + setKey(e.target.value)} + onChange={(e) => setKey(changeSpacesToDash(e.target.value))} /> @@ -48,9 +67,14 @@ export default function SecretNew() { }} /> - + + + +
); diff --git a/src/routes/SecretShow.tsx b/src/routes/SecretShow.tsx index b600d01f9..707f0d143 100644 --- a/src/routes/SecretShow.tsx +++ b/src/routes/SecretShow.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; -import { Stack, Table } from 'react-bootstrap'; +import { Stack, Table, Button } from 'react-bootstrap'; import HttpService from '../services/HttpService'; import { Secret } from '../interfaces'; import ButtonWithConfirmation from '../components/ButtonWithConfirmation'; @@ -10,10 +10,7 @@ export default function SecretShow() { const params = useParams(); const [secret, setSecret] = useState(null); - - const navigateToSecrets = (_result: any) => { - navigate(`/admin/secrets`); - }; + const [secretValue, setSecretValue] = useState(secret?.value); useEffect(() => { HttpService.makeCallToBackend({ @@ -22,6 +19,37 @@ export default function SecretShow() { }); }, [params]); + const handleSecretValueChange = (event: any) => { + if (secret) { + setSecretValue(event.target.value); + } + }; + + // const reloadSecret = (_result: any) => { + // window.location.reload(); + // }; + + const updateSecretValue = () => { + if (secret && secretValue) { + secret.value = secretValue; + HttpService.makeCallToBackend({ + path: `/secrets/${secret.key}`, + successCallback: () => { + setSecret(secret); + }, + httpMethod: 'PUT', + postBody: { + value: secretValue, + creator_user_id: secret.creator_user_id, + }, + }); + } + }; + + const navigateToSecrets = (_result: any) => { + navigate(`/admin/secrets`); + }; + const deleteSecret = () => { if (secret === null) { return; @@ -34,17 +62,18 @@ export default function SecretShow() { }; if (secret) { - const secretToUse = secret as any; - return ( <> -

Secret Key: {secretToUse.key}

+

Secret Key: {secret.key}

+
ID Secret KeySecret Value CreatorDelete
@@ -57,7 +86,15 @@ export default function SecretShow() { - +
{params.key}{secretToUse.value} + +
diff --git a/src/routes/TaskShow.tsx b/src/routes/TaskShow.tsx index 7db07d84d..21954602c 100644 --- a/src/routes/TaskShow.tsx +++ b/src/routes/TaskShow.tsx @@ -17,7 +17,6 @@ export default function TaskShow() { const setErrorMessage = (useContext as any)(ErrorContext)[1]; useEffect(() => { - setErrorMessage(''); HttpService.makeCallToBackend({ path: `/tasks/${params.process_instance_id}/${params.task_id}`, successCallback: setTask, diff --git a/src/services/HttpService.ts b/src/services/HttpService.ts index bd4c3f930..075956bcf 100644 --- a/src/services/HttpService.ts +++ b/src/services/HttpService.ts @@ -67,11 +67,15 @@ backendCallProps) => { }); let isSuccessful = true; + let is403 = false; fetch(`${BACKEND_BASE_URL}${path}`, httpArgs) .then((response) => { if (response.status === 401) { UserService.doLogin(); throw new UnauthenticatedError('You must be authenticated to do this.'); + } else if (response.status === 403) { + is403 = true; + isSuccessful = false; } else if (!response.ok) { isSuccessful = false; } @@ -80,6 +84,10 @@ backendCallProps) => { .then((result: any) => { if (isSuccessful) { successCallback(result); + } else if (is403) { + // Hopefully we can make this service a hook and use the error message context directly + // eslint-disable-next-line no-alert + alert(result.message); } else { let message = 'A server error occurred.'; if (result.message) { @@ -89,6 +97,8 @@ backendCallProps) => { failureCallback(message); } else { console.error(message); + // eslint-disable-next-line no-alert + alert(message); } } })