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
This commit is contained in:
Jon Herron 2022-10-13 20:45:09 -04:00
parent 3cfc1bd383
commit a087961fab
8 changed files with 133 additions and 25 deletions

20
package-lock.json generated
View File

@ -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",

View File

@ -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",

View File

@ -1,7 +1,8 @@
export interface Secret {
id: number;
key: string;
value: string;
username: string;
creator_user_id: string;
}
export interface RecentProcessModel {

View File

@ -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 (
<tr key={(row as any).key}>
<td>
<Link to={`/admin/secrets/${(row as any).key}`}>
{(row as any).id}
</Link>
</td>
<td>
<Link to={`/admin/secrets/${(row as any).key}`}>
{(row as any).key}
</Link>
</td>
<td>{(row as any).value}</td>
<td>{(row as any).username}</td>
<td>
<MdDelete onClick={() => handleDeleteSecret((row as any).key)} />
</td>
</tr>
);
});
@ -41,9 +61,10 @@ export default function SecretList() {
<Table striped bordered>
<thead>
<tr>
<th>ID</th>
<th>Secret Key</th>
<th>Secret Value</th>
<th>Creator</th>
<th>Delete</th>
</tr>
</thead>
<tbody>{rows}</tbody>
@ -72,12 +93,11 @@ export default function SecretList() {
if (pagination) {
return (
<>
<Button href="/admin/secrets/new">Add a secret</Button>
<br />
<br />
<div>
<h2>Secrets</h2>
{SecretsDisplayArea()}
</>
<Button href="/admin/secrets/new">Add a secret</Button>
</div>
);
}
return null;

View File

@ -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 (
<main style={{ padding: '1rem 0' }}>
<h2>Add Secret</h2>
<Form onSubmit={addSecret}>
<Form.Group className="mb-3" controlId="formDisplayName">
<Form.Label>Key:</Form.Label>
<Form.Label>
Key: <span style={warningStyle}>No Spaces</span>
</Form.Label>
<Form.Control
type="text"
value={key}
onChange={(e) => setKey(e.target.value)}
onChange={(e) => setKey(changeSpacesToDash(e.target.value))}
/>
</Form.Group>
<Form.Group className="mb-3" controlId="formIdentifier">
@ -48,9 +67,14 @@ export default function SecretNew() {
}}
/>
</Form.Group>
<Button variant="primary" type="submit">
Submit
</Button>
<Stack direction="horizontal" gap={3}>
<Button variant="primary" type="submit">
Submit
</Button>
<Button variant="danger" type="button" onClick={navigateToSecrets}>
Cancel
</Button>
</Stack>
</Form>
</main>
);

View File

@ -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<Secret | null>(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 (
<>
<Stack direction="horizontal" gap={3}>
<h2>Secret Key: {secretToUse.key}</h2>
<h2>Secret Key: {secret.key}</h2>
<ButtonWithConfirmation
description="Delete Secret?"
onConfirmation={deleteSecret}
buttonLabel="Delete"
/>
<Button variant="warning" onClick={updateSecretValue}>
Update Value
</Button>
</Stack>
<div>
<Table striped bordered>
@ -57,7 +86,15 @@ export default function SecretShow() {
<tbody>
<tr>
<td>{params.key}</td>
<td>{secretToUse.value}</td>
<td>
<input
id="secret_value"
name="secret_value"
type="text"
value={secretValue || secret.value}
onChange={handleSecretValueChange}
/>
</td>
</tr>
</tbody>
</Table>

View File

@ -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,

View File

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