diff --git a/spiffworkflow-frontend/package-lock.json b/spiffworkflow-frontend/package-lock.json index 765c27902..f6b8687d9 100644 --- a/spiffworkflow-frontend/package-lock.json +++ b/spiffworkflow-frontend/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/spiffworkflow-frontend/package.json b/spiffworkflow-frontend/package.json index 4394c25fc..2ce2d7a13 100644 --- a/spiffworkflow-frontend/package.json +++ b/spiffworkflow-frontend/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/spiffworkflow-frontend/src/interfaces.ts b/spiffworkflow-frontend/src/interfaces.ts index 2e023c9a2..a628940ba 100644 --- a/spiffworkflow-frontend/src/interfaces.ts +++ b/spiffworkflow-frontend/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/spiffworkflow-frontend/src/routes/SecretList.tsx b/spiffworkflow-frontend/src/routes/SecretList.tsx index b7a85b190..2b9e756ad 100644 --- a/spiffworkflow-frontend/src/routes/SecretList.tsx +++ b/spiffworkflow-frontend/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/spiffworkflow-frontend/src/routes/SecretNew.tsx b/spiffworkflow-frontend/src/routes/SecretNew.tsx index 01c8de5bf..7a697a98a 100644 --- a/spiffworkflow-frontend/src/routes/SecretNew.tsx +++ b/spiffworkflow-frontend/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/spiffworkflow-frontend/src/routes/SecretShow.tsx b/spiffworkflow-frontend/src/routes/SecretShow.tsx index b600d01f9..707f0d143 100644 --- a/spiffworkflow-frontend/src/routes/SecretShow.tsx +++ b/spiffworkflow-frontend/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/spiffworkflow-frontend/src/routes/TaskShow.tsx b/spiffworkflow-frontend/src/routes/TaskShow.tsx index 7db07d84d..21954602c 100644 --- a/spiffworkflow-frontend/src/routes/TaskShow.tsx +++ b/spiffworkflow-frontend/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/spiffworkflow-frontend/src/services/HttpService.ts b/spiffworkflow-frontend/src/services/HttpService.ts index bd4c3f930..075956bcf 100644 --- a/spiffworkflow-frontend/src/services/HttpService.ts +++ b/spiffworkflow-frontend/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); } } })